简体   繁体   中英

HTTP client returns random errors on timeout

I have an HTTP client with a custom RoundTripper which in turn uses the http.DefaultTransport to handle the request. Now imagine I have a slow server which takes a long time to respond and it makes my http client timeout and cancel the client. Here is the code for the client:

package main

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

type rt struct {
    roundTripper func(req *http.Request) (*http.Response, error)
}

func (r rt) RoundTrip(req *http.Request) (*http.Response, error) {
    return r.roundTripper(req)
}

func main() {

    c := http.Client{
        Timeout:   3 * time.Second,
        Transport:  rt{RoundTripper(http.DefaultTransport)},
    }
    resp, err := c.Get("http://127.0.0.1:9000")
    if err != nil {
        fmt.Println("err:", err)
    } else {
        body, err := ioutil.ReadAll(resp.Body)
        resp.Body.Close()
        fmt.Println(string(body), err)
    }

}
func RoundTripper(next http.RoundTripper) func(req *http.Request) (*http.Response, error) {
    return func(req *http.Request) (*http.Response, error) {
        resp, err := next.RoundTrip(req)
        if err != nil {
            return nil, fmt.Errorf("err: %w", err)
        }
        return resp, nil
    }
}

The problem here is that the error I'm receiving on timeout is randomly one of net/http: request canceled or context deadline exceeded .
Now I know they should be semantically the same thing but I'm failing to understand why it's returning each and when?

Here is the server code if you want to try it for yourself.

The function net/http/client.setRequestCancel() is used to set the cancel of the request. There are three ways

  • The second will return: net/http: request canceled
  • The third will return: context deadline exceeded

Because both use the same deadline, time.now()+client.Timeout . So according to the runtime schedule, the request will be cancelled randomly through these two methods.

    https://github.com/golang/go/blob/master/src/net/http/transport.go#L2652

    case <-cancelChan:
        // return err: net/http: request
        pc.t.CancelRequest(req.Request) canceled
        cancelChan = nil
    case <-ctxDoneChan:
        // return err:
        pc.t.cancelRequest(req.Request, req.Context().Err())
        cancelChan = nil
        ctxDoneChan = nil

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM