Last active
February 1, 2018 15:50
-
-
Save bg5sbk/9144886 to your computer and use it in GitHub Desktop.
How to set timeout for http.Get() in golang.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// How to set timeout for http.Get() in golang | |
// | |
package main | |
import ( | |
"io" | |
"io/ioutil" | |
"log" | |
"net" | |
"net/http" | |
"sync" | |
"time" | |
) | |
type TimeoutConn struct { | |
conn net.Conn | |
timeout time.Duration | |
} | |
func NewTimeoutConn(conn net.Conn, timeout time.Duration) *TimeoutConn { | |
return &TimeoutConn{ | |
conn: conn, | |
timeout: timeout, | |
} | |
} | |
func (c *TimeoutConn) Read(b []byte) (n int, err error) { | |
c.SetReadDeadline(time.Now().Add(c.timeout)) | |
return c.conn.Read(b) | |
} | |
func (c *TimeoutConn) Write(b []byte) (n int, err error) { | |
c.SetWriteDeadline(time.Now().Add(c.timeout)) | |
return c.conn.Write(b) | |
} | |
func (c *TimeoutConn) Close() error { | |
return c.conn.Close() | |
} | |
func (c *TimeoutConn) LocalAddr() net.Addr { | |
return c.conn.LocalAddr() | |
} | |
func (c *TimeoutConn) RemoteAddr() net.Addr { | |
return c.conn.RemoteAddr() | |
} | |
func (c *TimeoutConn) SetDeadline(t time.Time) error { | |
return c.conn.SetDeadline(t) | |
} | |
func (c *TimeoutConn) SetReadDeadline(t time.Time) error { | |
return c.conn.SetReadDeadline(t) | |
} | |
func (c *TimeoutConn) SetWriteDeadline(t time.Time) error { | |
return c.conn.SetWriteDeadline(t) | |
} | |
func main() { | |
client := &http.Client{ | |
Transport: &http.Transport{ | |
Dial: func(netw, addr string) (net.Conn, error) { | |
log.Printf("dial to %s://%s", netw, addr) | |
conn, err := net.DialTimeout(netw, addr, time.Second*2) | |
if err != nil { | |
return nil, err | |
} | |
return NewTimeoutConn(conn, time.Second*2), nil | |
}, | |
ResponseHeaderTimeout: time.Second * 2, | |
}, | |
} | |
addr := StartTestServer() | |
SendTestRequest(client, "1st", addr, "normal") | |
SendTestRequest(client, "2st", addr, "normal") | |
SendTestRequest(client, "3st", addr, "timeout") | |
SendTestRequest(client, "4st", addr, "normal") | |
time.Sleep(time.Second * 3) | |
SendTestRequest(client, "5st", addr, "normal") | |
} | |
func StartTestServer() string { | |
listener, err := net.Listen("tcp", ":0") | |
if err != nil { | |
log.Fatalf("failed to listen - %s", err.Error()) | |
} | |
wg := new(sync.WaitGroup) | |
wg.Add(1) | |
go func() { | |
http.HandleFunc("/normal", func(w http.ResponseWriter, req *http.Request) { | |
time.Sleep(1000 * time.Millisecond) | |
io.WriteString(w, "ok") | |
}) | |
http.HandleFunc("/timeout", func(w http.ResponseWriter, req *http.Request) { | |
time.Sleep(2500 * time.Millisecond) | |
io.WriteString(w, "ok") | |
}) | |
wg.Done() | |
err = http.Serve(listener, nil) | |
if err != nil { | |
log.Fatalf("failed to start HTTP server - %s", err.Error()) | |
} | |
}() | |
wg.Wait() | |
log.Printf("start http server at http://%s/", listener.Addr()) | |
return listener.Addr().String() | |
} | |
func SendTestRequest(client *http.Client, id, addr, path string) { | |
req, err := http.NewRequest("GET", "http://"+addr+"/"+path, nil) | |
if err != nil { | |
log.Fatalf("new request failed - %s", err) | |
} | |
req.Header.Add("Connection", "keep-alive") | |
switch path { | |
case "normal": | |
if resp, err := client.Do(req); err != nil { | |
log.Fatalf("%s request failed - %s", id, err) | |
} else { | |
result, err2 := ioutil.ReadAll(resp.Body) | |
if err2 != nil { | |
log.Fatalf("%s response read failed - %s", id, err2) | |
} | |
resp.Body.Close() | |
log.Printf("%s request - %s", id, result) | |
} | |
case "timeout": | |
if _, err := client.Do(req); err == nil { | |
log.Fatalf("%s request not timeout", id) | |
} else { | |
log.Printf("%s request - %s", id, err) | |
} | |
} | |
} |
Very handy, thank you.
Since go 1.3 you can simply do
timeout := time.Duration(5 * time.Second)
client := http.Client{
Timeout: timeout,
}
client.Get(url)
@mat That works in this case, but doesn't help if you want to set the timeout for each individual Read() or Write(). You'll have to update the deadline before every call.
Why not just use Context
?
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Are you sure that setReadDeadline and setWriteDeadline do anything different from setDeadline in the other gist? (https://gist.github.com/dmichael/5710968) ?