本文中的方案是有缺陷的,本文目前只当成历史记录,完整方案请参考(这篇)[http://1234n.com/?post/mwsw2r]
Go自带的http包中提供了很完整的HTTP客户端和服务端功能。最近项目有几个需求需要从游戏服务端发起HTTP请求来调用运营商提供的接口。用Go语言实现起来超简单,http.Get()调一下就行了。
但是,http.Get()是没提供参数让调用者设置连接和读写的超时,项目在线上就遇到了永久阻塞在http.Get()不返回的情况。
上网找了一下资料,最后解决了这个问题,以下是试验代码,先贴代码再分析原理(gist连接):
//
// How to set timeout for http.Get() in golang
//
package main
import (
"io"
"io/ioutil"
"log"
"net"
"net/http"
"time"
)
func StartTestServer() string {
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")
})
listener, err := net.Listen("tcp", ":0")
if err != nil {
log.Fatalf("failed to listen - %s", err.Error())
}
go func() {
err = http.Serve(listener, nil)
if err != nil {
log.Fatalf("failed to start HTTP server - %s", err.Error())
}
}()
log.Printf("start http server at http://%s/", listener.Addr())
return listener.Addr().String()
}
func main() {
addr := StartTestServer()
client := &http.Client{
Transport: &http.Transport{
Dial: func(netw, addr string) (net.Conn, error) {
conn, err := net.DialTimeout(netw, addr, time.Second*2)
if err != nil {
return nil, err
}
conn.SetDeadline(time.Now().Add(time.Second * 2))
return conn, nil
},
ResponseHeaderTimeout: time.Second * 2,
},
}
// 1st request
if resp, err := client.Get("http://" + addr + "/normal"); err != nil {
log.Fatalf("1st request failed - %s", err)
} else {
result, err2 := ioutil.ReadAll(resp.Body)
if err2 != nil {
log.Fatalf("1st response read failed - %s", err2)
}
resp.Body.Close()
log.Printf("1st request - %s", result)
}
// 2nd request
if _, err := client.Get("http://" + addr + "/timeout"); err == nil {
log.Fatalf("2nd not timeout")
} else {
log.Printf("2nd request - %s", err)
}
// 3rd request
if resp, err := client.Get("http://" + addr + "/normal"); err != nil {
log.Fatalf("3rd request - %s", err)
} else {
result, err2 := ioutil.ReadAll(resp.Body)
if err2 != nil {
log.Fatalf("3rd response read failed - %s", err2)
}
resp.Body.Close()
log.Printf("3rd request - %s", result)
}
}
代码中最主要的是创建http.Client的那一段,其中自定义了http.Client的Transport,而Transport创建时指定了一个拨号回调,在拨号回调中,使用DialTimeout来支持连接超时,当连接成功后,利用SetDeadline来让连接支持读写超时。
http包提供的http.Get实际上调用的是事先创建好的DefaultClient,而DefaultClient使用的则是默认的Transport,这一调用关系很容易从http包的代码中看出来。
默认的Client和Transport都没有做超时设置,所以我们需要自己创建http.Client来实现带超时功能的http客户。
http包还有一个比较容易坑到新手的坑点,就是请求后返回的http.Response,用完必须调用Body.Close(),文档上有写了,但是很容易被忽略,并且Close方法不是在Response类型上的,而是在Body属性上。
如果没有调用Body.Close(),http请求所用的tcp连接就不会释放,最后就会出现连数过多。
有疑问加站长微信联系(非本文作者)