Skip to content

net/http: errors from Client.Timeout triggering have .Timeout()==false #9405

Closed
@tv42

Description

@tv42

(From aaronlevy on IRC)

It's hard for a client to know when a HTTP request failed due to timeout, and when for other reasons.

Given a slow server

package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        time.Sleep(3*time.Second)
        fmt.Fprintf(w, "req rcvd")
    })
    http.ListenAndServe(":8080", nil)
}

and a client

package main

import (
    "fmt"
    "io/ioutil"
    "net"
    "net/http"
    "net/url"
    "time"
)

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

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

    resp, err := ioutil.ReadAll(req.Body)
    req.Body.Close()
    if err != nil {
        fmt.Println(err)
    }
    fmt.Printf("%s", resp)
}

Results in

$ go run server.go &
$ 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

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions