<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Http on Roberto Selbach</title><link>https://rselbach.com/tags/http/</link><description>Recent content in Http on Roberto Selbach</description><generator>Hugo</generator><language>en-US</language><lastBuildDate>Thu, 30 Oct 2014 18:50:00 +0000</lastBuildDate><atom:link href="https://rselbach.com/tags/http/index.xml" rel="self" type="application/rss+xml"/><item><title>Where an HTTP timeout actually fires</title><link>https://rselbach.com/where-an-http-timeout-actually-fires/</link><pubDate>Thu, 30 Oct 2014 18:50:00 +0000</pubDate><guid>https://rselbach.com/where-an-http-timeout-actually-fires/</guid><description>&lt;p&gt;I added a dial timeout to an HTTP client and assumed I had bounded the entire request. Then a server accepted the connection, sent headers, and dribbled the body slowly enough to occupy a worker for minutes. The timeout had completed its duty before the interesting failure began.&lt;/p&gt;
&lt;p&gt;In Go 1.3, the first choice for a simple end-to-end limit is &lt;code&gt;http.Client.Timeout&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;client&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;{&lt;span style="color:#a6e22e"&gt;Timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It covers connection setup, redirects, and reading the response body. If the policy really is “this request gets five seconds,” start there. My narrower transport setting looked like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;tr&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Transport&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;Dial&lt;/span&gt;: (&lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Dialer&lt;/span&gt;{
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;Timeout&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}).&lt;span style="color:#a6e22e"&gt;Dial&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;client&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Client&lt;/span&gt;{&lt;span style="color:#a6e22e"&gt;Transport&lt;/span&gt;: &lt;span style="color:#a6e22e"&gt;tr&lt;/span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The dial timeout covers establishing the network connection. It does not cover waiting for response headers, reading a response body, or the time spent following redirects. DNS resolution may consume part of dialing behavior depending on the platform and resolver path, but the timeout is still not an end-to-end request deadline.&lt;/p&gt;
&lt;p&gt;To see the phases, I wrote a deliberately bad server:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;func&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;slow&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;ResponseWriter&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Request&lt;/span&gt;) {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Header&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;Set&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;text/plain&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;WriteHeader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusOK&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;.(&lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Flusher&lt;/span&gt;); &lt;span style="color:#a6e22e"&gt;ok&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;		&lt;span style="color:#a6e22e"&gt;f&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Flush&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Sleep&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Fprintln&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;w&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;eventually&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The client connected immediately and received headers immediately. Its &lt;code&gt;Do&lt;/code&gt; call returned a response, then reading &lt;code&gt;resp.Body&lt;/code&gt; waited. A connect timeout could not help because the connection was exemplary.&lt;/p&gt;
&lt;p&gt;The transport exposes other phase-specific controls. &lt;code&gt;ResponseHeaderTimeout&lt;/code&gt; bounds the wait for response headers after the request is written. &lt;code&gt;TLSHandshakeTimeout&lt;/code&gt; bounds the TLS handshake. Neither bounds a slowly arriving body. These knobs are useful because failures differ: a slow handshake may indicate different capacity trouble than a handler that never writes headers.&lt;/p&gt;
&lt;p&gt;For policies that &lt;code&gt;Client.Timeout&lt;/code&gt; cannot express, I can arrange cancellation through the transport&amp;rsquo;s request cancellation support and a timer. Merely returning from a selecting caller while leaving &lt;code&gt;Do&lt;/code&gt; running leaks work: timeout handling must interrupt the network operation, not only stop waiting for its answer.&lt;/p&gt;
&lt;p&gt;For direct &lt;code&gt;net.Conn&lt;/code&gt; code, deadlines are clearer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;conn&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;SetDeadline&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Now&lt;/span&gt;().&lt;span style="color:#a6e22e"&gt;Add&lt;/span&gt;(&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;time&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Second&lt;/span&gt;)); &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The deadline applies to future reads and writes and causes them to fail after the time. &lt;code&gt;SetReadDeadline&lt;/code&gt; and &lt;code&gt;SetWriteDeadline&lt;/code&gt; split those directions. A deadline is an absolute time, not an idle timeout that automatically moves after every successful byte. For an idle protocol I refresh it after each accepted unit of progress.&lt;/p&gt;
&lt;p&gt;HTTP complicates direct connection deadlines because &lt;code&gt;http.Transport&lt;/code&gt; owns and reuses pooled connections. A per-request caller should not reach underneath and set arbitrary deadlines on a connection that may later serve another request. Transport-level controls and cancellation preserve that ownership boundary.&lt;/p&gt;
&lt;p&gt;As with the earlier runtime-managed network mechanism, a goroutine can wait without occupying a thread. Cheap waiting is not a timeout policy, though. In-flight requests, response body size, queue length, and elapsed time still need deliberate limits.&lt;/p&gt;
&lt;p&gt;Servers have the same issue from the opposite side. A handler can be tied up by a client that sends or receives slowly. Depending on the Go version and server configuration, read and write timeouts establish connection-level deadlines around phases of serving a request. They are blunt instruments, especially for streaming responses, but leaving every deadline at zero means &amp;ldquo;forever.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I tested the repaired client with three bad endpoints: one that never completed a TCP connection, one that accepted but withheld headers, and one that streamed a byte periodically. Each exercises a different phase. The output made the distinction visible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dial stall: timeout awaiting connection
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;header stall: timeout awaiting response headers
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;body stall: request deadline reached while reading body
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Error strings vary and should not become program logic. I care that each operation terminates, closes its body or cancellation path, and leaves no steadily growing goroutine count.&lt;/p&gt;
&lt;p&gt;The temptation is to choose one five-second number and call the system safe. Real policies differ. A metadata request might have a strict total deadline. A large download may allow minutes overall but reject 30 seconds without progress. A streaming endpoint may be intentionally unbounded while still needing heartbeat and cancellation rules.&lt;/p&gt;
&lt;p&gt;Timeout errors should remain distinguishable from protocol failures. I check whether a returned network error reports &lt;code&gt;Timeout()&lt;/code&gt; rather than matching its text, then include the operation and peer in the higher-level error. Retrying blindly is still wrong: a timed-out request may have reached the server, so non-idempotent operations need application-level rules.&lt;/p&gt;
&lt;p&gt;“The HTTP timeout” is not a very useful phrase. There are limits for connecting, handshaking, receiving headers, reading bodies, remaining idle, and completing the whole operation. Naming the phase put the mechanism in the right place; before that, I had a very reliable timeout on the part that was not slow.&lt;/p&gt;</description></item><item><title>Draining HTTP response bodies</title><link>https://rselbach.com/draining-http-response-bodies/</link><pubDate>Tue, 04 Mar 2014 16:40:00 +0000</pubDate><guid>https://rselbach.com/draining-http-response-bodies/</guid><description>&lt;p&gt;I had a polling client that became steadily slower. The server was fine, DNS was fine, and my diagnostic method of staring at it was producing limited returns.&lt;/p&gt;
&lt;p&gt;The client checked only the status:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Get&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;url&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusCode&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;http&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;StatusOK&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;fmt&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Errorf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;status: %s&amp;#34;&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Status&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Closing the body is necessary, but for persistent HTTP connections it is not always sufficient. If I do not read the response body to EOF, the transport may be unable to reuse that TCP connection. My server returned a small explanatory body for an error status; I ignored it and paid for a new connection on the next poll.&lt;/p&gt;
&lt;p&gt;For responses whose contents I deliberately discard, I now do this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;defer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Close&lt;/span&gt;()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Copy&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ioutil&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Discard&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;); &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; &lt;span style="color:#f92672"&gt;!=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;nil&lt;/span&gt; {
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;	&lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The transport owns a pool of persistent connections keyed by destination. Reading through EOF lets its body wrapper observe completion and return the connection to the idle pool. &lt;code&gt;Close&lt;/code&gt; still matters because it releases resources on every exit path.&lt;/p&gt;
&lt;p&gt;This is not permission to drain an unbounded hostile response. If I only need to preserve reuse for a known-small error body, I put a limit around it and accept that an oversized body may force the connection closed:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;r&lt;/span&gt; &lt;span style="color:#f92672"&gt;:=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;LimitReader&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;resp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Body&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;_&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;err&lt;/span&gt; = &lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Copy&lt;/span&gt;(&lt;span style="color:#a6e22e"&gt;ioutil&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;Discard&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;r&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The same ownership rule applies on success. The caller that receives &lt;code&gt;*http.Response&lt;/code&gt; generally owns &lt;code&gt;Body&lt;/code&gt; and must close it. A helper that consumes the body should close it itself and return decoded data instead. Splitting those responsibilities is how bodies remain open.&lt;/p&gt;
&lt;p&gt;I verified reuse by wrapping &lt;code&gt;Transport.Dial&lt;/code&gt; with a counter. Ten requests to the same server produced ten dials before the fix and one after it. That is a crude instrument, but better than inferring connection behavior from elapsed time. A server can close persistent connections itself, so one dial is not a universal expected value.&lt;/p&gt;
&lt;p&gt;There is a related server-side rule: a handler should not assume unread request bytes are harmless. &lt;code&gt;net/http&lt;/code&gt; often manages request bodies for handlers, but large or malformed input still needs explicit size limits. Connection reuse is valuable; reading arbitrary quantities merely to obtain it is not.&lt;/p&gt;
&lt;p&gt;After draining the tiny responses, repeated requests settled onto the same few connections instead of continually dialing. It was not an exotic kernel problem. It was one unread paragraph of error text, patiently converting itself into sockets.&lt;/p&gt;</description></item></channel></rss>