這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
本文中的方案是有缺陷的,本文目前只當成記錄,完整方案請參考(這篇)[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 mainimport ( "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串連就不會釋放,最後就會出現連數過多。