mreiferson/go-httpclient

an easier way?

Closed this issue · 2 comments

After further investigation, I realized that there may be an easier path...

Instead of hacking around with custom protocols to allow us to hook into the built in HTTP client we could instead provide a custom dial function to http.Transport that returned our own custom type that satisfies the net.Conn interface and extended deadlines on every call to read/write. It could also use DialTimeout to provide connection timeout.

There are some benefits and tradeoffs, though:

  • You would no longer have access to the connection on the client side.
  • The amount of work that the built in HTTP client does may be overkill for some high-performance, high-volume, HTTP only internal applications. This is largely resolved by this package (no goroutine per connection and no additional allocations per request).
  • However, on the plus side, this would resolve any need to reproduce functionality such as HTTPS and all the other nice things the built in client provides.

It would look something like this:

type deadlinedConn struct {
    net.Conn
}

func (c *deadlinedConn) Read(b []byte) (n int, err error) {
    c.Conn.SetReadDeadline(time.Second)
    return c.Conn.Read(b)
}

func (c *deadlinedConn) Write(b []byte) (n int, err error) {
    c.Conn.SetWriteDeadline(time.Second)
    return c.Conn.Write(b)
}

transport := &http.Transport{
    Dial: func(netw, addr string) (net.Conn, error) {
        c, err := net.DialTimeout(netw, addr, time.Second)
        if err != nil {
            return nil, err
        }
        return &deadlinedConn{c}, nil
    },
}
httpclient := &http.Client{Transport: transport}

I do a similar thing in some of my HTTP clients and it leaves me with timeouts at weird times. I do think you need to make sure to reset the timeouts after each read and write, but it gets fairly confusing when you have a fully blocking read or write model in transport (which I believe is the case in the default transport), it's hard to tell what it means. You really need to enable the read timeouts only when there are pending HTTP requests that should have responses. Note that this is further complicated when you are writing a request that may or may not be sending back data at the same time.