<p dir="auto">I just reviewed a PR with the line <code class="notranslate">case err.Error() == "http: can't write HTTP request on broken connection"</code>. That's brittle code, and it's hard to test in a way that ensures it stays in sync with any changes to the error message wording in net/http--but there's no way to improve upon it given the errors the http package emits.</p>
<p dir="auto">Possible improments:</p>
<ul dir="auto">
<li>net/http could predeclare its errors</li>
<li>or make a custom error type</li>
</ul>
<p dir="auto">Any mechanism is fine as long as it lets the compiler check that people are referring to the error they intend to.</p>
<p dir="auto">(I realize this is probably low priority.)</p>
<p dir="auto">(From aaronlevy on IRC)</p>
<p dir="auto">It's hard for a client to know when a HTTP request failed due to timeout, and when for other reasons.</p>
<p dir="auto">Given a slow server</p>
<div class="highlight highlight-source-go notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="package main

import (
    &quot;fmt&quot;
    &quot;net/http&quot;
    &quot;time&quot;
)

func main() {
    http.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(3*time.Second)
        fmt.Fprintf(w, &quot;req rcvd&quot;)
    })
    http.ListenAndServe(&quot;:8080&quot;, nil)
}"><pre class="notranslate"><span class="pl-k">package</span> main

<span class="pl-k">import</span> (
    <span class="pl-s">"fmt"</span>
    <span class="pl-s">"net/http"</span>
    <span class="pl-s">"time"</span>
)

<span class="pl-k">func</span> <span class="pl-en">main</span>() {
    <span class="pl-s1">http</span>.<span class="pl-en">HandleFunc</span>(<span class="pl-s">"/"</span>, <span class="pl-k">func</span>(<span class="pl-s1">w</span> http.<span class="pl-smi">ResponseWriter</span>, <span class="pl-s1">r</span> <span class="pl-c1">*</span>http.<span class="pl-smi">Request</span>) {
        <span class="pl-s1">time</span>.<span class="pl-en">Sleep</span>(<span class="pl-c1">3</span><span class="pl-c1">*</span><span class="pl-s1">time</span>.<span class="pl-c1">Second</span>)
        <span class="pl-s1">fmt</span>.<span class="pl-en">Fprintf</span>(<span class="pl-s1">w</span>, <span class="pl-s">"req rcvd"</span>)
    })
    <span class="pl-s1">http</span>.<span class="pl-en">ListenAndServe</span>(<span class="pl-s">":8080"</span>, <span class="pl-c1">nil</span>)
}</pre></div>
<p dir="auto">and a client</p>
<div class="highlight highlight-source-go notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="package main

import (
    &quot;fmt&quot;
    &quot;io/ioutil&quot;
    &quot;net&quot;
    &quot;net/http&quot;
    &quot;net/url&quot;
    &quot;time&quot;
)

func main() {
    client := &amp;http.Client{
        Timeout: time.Duration(time.Second),
    }

    req, err := client.Get(&quot;http://localhost:8080&quot;)
    if err != nil {
        fmt.Printf(&quot;type: %T\n&quot;, err)
        fmt.Printf(&quot;error: %v\n&quot;, err)
        if err2, ok := err.(*url.Error); ok {
            fmt.Printf(&quot;inner type: %T\n&quot;, err2.Err)
            fmt.Printf(&quot;inner error: %v\n&quot;, err2.Err)
            if err3, ok := err2.Err.(net.Error); ok {
                fmt.Printf(&quot;is timeout: %v\n&quot;, err3.Timeout())
            }
            if err4, ok := err2.Err.(*net.OpError); ok {
                fmt.Printf(&quot;OpError inner type: %T\n&quot;, err4.Err)
                fmt.Printf(&quot;OpError inner error: %v\n&quot;, err4.Err)
            }
        }
        return
    }

    resp, err := ioutil.ReadAll(req.Body)
    req.Body.Close()
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf(&quot;%s&quot;, resp)
}"><pre class="notranslate"><span class="pl-k">package</span> main

<span class="pl-k">import</span> (
    <span class="pl-s">"fmt"</span>
    <span class="pl-s">"io/ioutil"</span>
    <span class="pl-s">"net"</span>
    <span class="pl-s">"net/http"</span>
    <span class="pl-s">"net/url"</span>
    <span class="pl-s">"time"</span>
)

<span class="pl-k">func</span> <span class="pl-en">main</span>() {
    <span class="pl-s1">client</span> <span class="pl-c1">:=</span> <span class="pl-c1">&amp;</span>http.<span class="pl-smi">Client</span>{
        <span class="pl-c1">Timeout</span>: <span class="pl-s1">time</span>.<span class="pl-en">Duration</span>(<span class="pl-s1">time</span>.<span class="pl-c1">Second</span>),
    }

    <span class="pl-s1">req</span>, <span class="pl-s1">err</span> <span class="pl-c1">:=</span> <span class="pl-s1">client</span>.<span class="pl-en">Get</span>(<span class="pl-s">"http://localhost:8080"</span>)
    <span class="pl-k">if</span> <span class="pl-s1">err</span> <span class="pl-c1">!=</span> <span class="pl-c1">nil</span> {
        <span class="pl-s1">fmt</span>.<span class="pl-en">Printf</span>(<span class="pl-s">"type: %T<span class="pl-cce">\n</span>"</span>, <span class="pl-s1">err</span>)
        <span class="pl-s1">fmt</span>.<span class="pl-en">Printf</span>(<span class="pl-s">"error: %v<span class="pl-cce">\n</span>"</span>, <span class="pl-s1">err</span>)
        <span class="pl-k">if</span> <span class="pl-s1">err2</span>, <span class="pl-s1">ok</span> <span class="pl-c1">:=</span> <span class="pl-s1">err</span>.(<span class="pl-c1">*</span>url.<span class="pl-smi">Error</span>); <span class="pl-s1">ok</span> {
            <span class="pl-s1">fmt</span>.<span class="pl-en">Printf</span>(<span class="pl-s">"inner type: %T<span class="pl-cce">\n</span>"</span>, <span class="pl-s1">err2</span>.<span class="pl-c1">Err</span>)
            <span class="pl-s1">fmt</span>.<span class="pl-en">Printf</span>(<span class="pl-s">"inner error: %v<span class="pl-cce">\n</span>"</span>, <span class="pl-s1">err2</span>.<span class="pl-c1">Err</span>)
            <span class="pl-k">if</span> <span class="pl-s1">err3</span>, <span class="pl-s1">ok</span> <span class="pl-c1">:=</span> <span class="pl-s1">err2</span>.<span class="pl-c1">Err</span>.(net.<span class="pl-smi">Error</span>); <span class="pl-s1">ok</span> {
                <span class="pl-s1">fmt</span>.<span class="pl-en">Printf</span>(<span class="pl-s">"is timeout: %v<span class="pl-cce">\n</span>"</span>, <span class="pl-s1">err3</span>.<span class="pl-en">Timeout</span>())
            }
            <span class="pl-k">if</span> <span class="pl-s1">err4</span>, <span class="pl-s1">ok</span> <span class="pl-c1">:=</span> <span class="pl-s1">err2</span>.<span class="pl-c1">Err</span>.(<span class="pl-c1">*</span>net.<span class="pl-smi">OpError</span>); <span class="pl-s1">ok</span> {
                <span class="pl-s1">fmt</span>.<span class="pl-en">Printf</span>(<span class="pl-s">"OpError inner type: %T<span class="pl-cce">\n</span>"</span>, <span class="pl-s1">err4</span>.<span class="pl-c1">Err</span>)
                <span class="pl-s1">fmt</span>.<span class="pl-en">Printf</span>(<span class="pl-s">"OpError inner error: %v<span class="pl-cce">\n</span>"</span>, <span class="pl-s1">err4</span>.<span class="pl-c1">Err</span>)
            }
        }
        <span class="pl-k">return</span>
    }

    <span class="pl-s1">resp</span>, <span class="pl-s1">err</span> <span class="pl-c1">:=</span> <span class="pl-s1">ioutil</span>.<span class="pl-en">ReadAll</span>(<span class="pl-s1">req</span>.<span class="pl-c1">Body</span>)
    <span class="pl-s1">req</span>.<span class="pl-c1">Body</span>.<span class="pl-en">Close</span>()
    <span class="pl-k">if</span> <span class="pl-s1">err</span> <span class="pl-c1">!=</span> <span class="pl-c1">nil</span> {
        <span class="pl-s1">fmt</span>.<span class="pl-en">Println</span>(<span class="pl-s1">err</span>)
    }
    <span class="pl-s1">fmt</span>.<span class="pl-en">Printf</span>(<span class="pl-s">"%s"</span>, <span class="pl-s1">resp</span>)
}</pre></div>
<p dir="auto">Results in</p>
<div class="highlight highlight-text-shell-session notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="$ go run server.go &amp;
$ go run client.go
type: *url.Error
error: Get http://localhost:8080: read tcp 127.0.0.1:8080: use of closed network connection
inner type: *net.OpError
inner error: read tcp 127.0.0.1:8080: use of closed network connection
is timeout: false
OpError inner type: *errors.errorString
OpError inner error: use of closed network connection"><pre class="notranslate">$ <span class="pl-s1">go run server.go <span class="pl-k">&amp;</span></span>
$ <span class="pl-s1">go run client.go</span>
<span class="pl-c1">type: *url.Error</span>
<span class="pl-c1">error: Get http://localhost:8080: read tcp 127.0.0.1:8080: use of closed network connection</span>
<span class="pl-c1">inner type: *net.OpError</span>
<span class="pl-c1">inner error: read tcp 127.0.0.1:8080: use of closed network connection</span>
<span class="pl-c1">is timeout: false</span>
<span class="pl-c1">OpError inner type: *errors.errorString</span>
<span class="pl-c1">OpError inner error: use of closed network connection</span></pre></div>
<p dir="auto">That's quite the gift wrapping on that error. But the real bug is "is timeout: false". The error was caused by a timeout, it should claim to be one.</p>