這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
Go net/http包
Go Http用戶端
get請求可以直接http.Get方法
package mainimport ("fmt""net/http""log""reflect""bytes")func main() {resp, err := http.Get("http://www.baidu.com")if err != nil {// handle errorlog.Println(err)return}defer resp.Body.Close()headers := resp.Headerfor k, v := range headers {fmt.Printf("k=%v, v=%v\n", k, v)}fmt.Printf("resp status %s,statusCode %d\n", resp.Status, resp.StatusCode)fmt.Printf("resp Proto %s\n", resp.Proto)fmt.Printf("resp content length %d\n", resp.ContentLength)fmt.Printf("resp transfer encoding %v\n", resp.TransferEncoding)fmt.Printf("resp Uncompressed %t\n", resp.Uncompressed)fmt.Println(reflect.TypeOf(resp.Body)) // *http.gzipReaderbuf := bytes.NewBuffer(make([]byte, 0, 512))length, _ := buf.ReadFrom(resp.Body)fmt.Println(len(buf.Bytes()))fmt.Println(length)fmt.Println(string(buf.Bytes()))}
有時需要在請求的時候設定頭參數、cookie之類的資料,就可以使用http.Do方法。
package mainimport ("net/http""strings""fmt""io/ioutil""log""encoding/json")func main() {client := &http.Client{}req, err := http.NewRequest("POST", "http://www.maimaiche.com/loginRegister/login.do",strings.NewReader("mobile=xxxxxxxxx&isRemberPwd=1"))if err != nil {log.Println(err)return}req.Header.Set("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")resp, err := client.Do(req)defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)if err != nil {log.Println(err)return}fmt.Println(resp.Header.Get("Content-Type")) //application/json;charset=UTF-8type Result struct {Msg stringStatus stringObj string}result := &Result{}json.Unmarshal(body, result) //解析json字串if result.Status == "1" {fmt.Println(result.Msg)} else {fmt.Println("login error")}fmt.Println(result)}
如果使用http POST方法可以直接使用http.Post 或 http.PostForm,
package mainimport ("net/http""strings""fmt""io/ioutil")func main() {resp, err := http.Post("http://www.maimaiche.com/loginRegister/login.do","application/x-www-form-urlencoded",strings.NewReader("mobile=xxxxxxxxxx&isRemberPwd=1"))if err != nil {fmt.Println(err)return}defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println(string(body))}
http.PostForm方法,
package mainimport ("net/http""fmt""io/ioutil""net/url")func main() {postParam := url.Values{"mobile": {"xxxxxx"},"isRemberPwd": {"1"},}resp, err := http.PostForm("http://www.maimaiche.com/loginRegister/login.do", postParam)if err != nil {fmt.Println(err)return}defer resp.Body.Close()body, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println(err)return}fmt.Println(string(body))}
Go Http伺服器端
一切的基礎:ServeMux 和 Handler
Go 語言中處理 HTTP 要求主要跟兩個東西相關:ServeMux 和 Handler。
ServrMux 本質上是一個 HTTP 要求路由器(或者叫多工器,Multiplexor)。它把收到的請求與一組預先定義的 URL 路徑列表做對比,然後在匹配到路徑的時候調用關聯的處理器(Handler)。
處理器(Handler)負責輸出HTTP響應的頭和本文。任何滿足了http.Handler介面的對象都可作為一個處理器。通俗的說,對象只要有個如下籤名的ServeHTTP方法即可:
ServeHTTP(http.ResponseWriter, *http.Request)
Go 語言的 HTTP 包內建了幾個函數用作常用處理器,比如FileServer,NotFoundHandler 和 RedirectHandler。我們從一個簡單具體的例子開始:
package mainimport ("log""net/http")func main() {mux := http.NewServeMux()rh := http.RedirectHandler("http://www.baidu.com", 307)mux.Handle("/foo", rh)log.Println("Listening...")http.ListenAndServe(":3000", mux)}
快速地過一下代碼:
- 在 main 函數中我們只用了 http.NewServeMux 函數來建立一個空的 ServeMux。
- 然後我們使用 http.RedirectHandler 函數建立了一個新的處理器,這個處理器會對收到的所有請求,都執行307重新導向操作到 http://www.baidu.com。
- 接下來我們使用 ServeMux.Handle 函數將處理器註冊到新建立的 ServeMux,所以它在 URL 路徑/foo 上收到所有的請求都交給這個處理器。
- 最後我們建立了一個新的伺服器,並通過 http.ListenAndServe 函數監聽所有進入的請求,通過傳遞剛才建立的 ServeMux來為請求去匹配對應處理器。
然後在瀏覽器中訪問 http://localhost:3000/foo,你應該能發現請求已經成功的重新導向了。
明察秋毫的你應該能注意到一些有意思的事情:ListenAndServer 的函數簽名是 ListenAndServe(addr string, handler Handler) ,但是第二個參數我們傳遞的是個 ServeMux。
我們之所以能這麼做,是因為 ServeMux 也有 ServeHTTP 方法,因此它也是個合法的 Handler。
對我來說,將 ServerMux 用作一個特殊的Handler是一種簡化。它不是自己輸出響應而是將請求傳遞給註冊到它的其他 Handler。這乍一聽起來不是什麼明顯的飛躍 - 但在 Go 中將 Handler 鏈在一起是非常普遍的用法。
自訂處理器(Custom Handlers)
讓我們建立一個自訂的處理器,功能是將以特定格式輸出當前的本地時間:
type timeHandler struct {format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(th.format)w.Write([]byte("The time is: " + tm))}
這個例子裡代碼本身並不是重點。
真正的重點是我們有一個對象(本例中就是個timerHandler結構體,但是也可以是一個字串、一個函數或者任意的東西),我們在這個對象上實現了一個 ServeHTTP(http.ResponseWriter, *http.Request) 簽名的方法,這就是我們建立一個處理器所需的全部東西。
我們把這個整合到具體的樣本裡:
//File: main.gopackage mainimport ("log""net/http""time")type timeHandler struct {format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(th.format)w.Write([]byte("The time is: " + tm))}func main() {mux := http.NewServeMux()th := &timeHandler{format: time.RFC1123}mux.Handle("/time", th)log.Println("Listening...")http.ListenAndServe(":3000", mux)}
main函數中,我們像初始化一個常規的結構體一樣,初始化了timeHandler,用 & 符號獲得了其地址。隨後,像之前的例子一樣,我們使用 mux.Handle 函數來將其註冊到 ServerMux。
現在當我們運行這個應用,ServerMux 將會將任何對 /time的請求直接交給 timeHandler.ServeHTTP 方法處理。
訪問一下這個地址看一下效果:http://localhost:3000/time 。
注意我們可以在多個路由中輕鬆的複用 timeHandler:
//File: main.gopackage mainimport ("log""net/http""time")type timeHandler struct {format string}func (th *timeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(th.format)w.Write([]byte("The time is: " + tm))}func main() {mux := http.NewServeMux()th1123 := &timeHandler{format: time.RFC1123}mux.Handle("/time/rfc1123", th1123)th3339 := &timeHandler{format: time.RFC3339}mux.Handle("/time/rfc3339", th3339)log.Println("Listening...")http.ListenAndServe(":3000", mux)}
將函數作為處理器
對於簡單的情況(比如上面的例子),定義個新的有 ServerHTTP 方法的自訂類型有些累贅。我們看一下另外一種方式,我們藉助 http.HandlerFunc 類型來讓一個常規函數滿足作為一個 Handler 介面的條件。
任何有 func(http.ResponseWriter, *http.Request) 簽名的函數都能轉化為一個 HandlerFunc 類型。這很有用,因為 HandlerFunc 對象內建了 ServeHTTP 方法,後者可以聰明又方便的調用我們最初提供的函數內容。
讓我們使用這個技術重新實現一遍timeHandler應用:
package mainimport ("log""net/http""time")func timeHandler(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(time.RFC1123)w.Write([]byte("The time is: " + tm))}func main() {mux := http.NewServeMux()// Convert the timeHandler function to a HandlerFunc typeth := http.HandlerFunc(timeHandler)// And add it to the ServeMuxmux.Handle("/time", th)log.Println("Listening...")http.ListenAndServe(":3000", mux)}
實際上,將一個函數轉換成 HandlerFunc 後註冊到 ServeMux 是很普遍的用法,所以 Go 語言為此提供了個便捷方式:ServerMux.HandlerFunc 方法。
我們使用便捷方式重寫 main() 函數看起來是這樣的:
package mainimport ("log""net/http""time")func timeHandler(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(time.RFC1123)w.Write([]byte("The time is: " + tm))}func main() {mux := http.NewServeMux()mux.HandleFunc("/time", timeHandler)log.Println("Listening...")http.ListenAndServe(":3000", mux)}
絕大多數情況下這種用函數當處理器的方式工作的很好。但是當事情開始變得更複雜的時候,就會有些產生一些限制了。
你可能已經注意到了,跟之前的方式不同,我們不得不將時間格式寫入程式碼到 timeHandler 的方法中。如果我們想從 main() 函數中傳遞一些資訊或者變數給處理器該怎麼辦?
一個優雅的方式是將我們處理器放到一個閉包中,將我們要使用的變數帶進去:
//File: main.gopackage mainimport ("log""net/http""time")func timeHandler(format string) http.Handler {fn := func(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(format)w.Write([]byte("The time is: " + tm))}return http.HandlerFunc(fn)}func main() {mux := http.NewServeMux()th := timeHandler(time.RFC1123)mux.Handle("/time", th)log.Println("Listening...")http.ListenAndServe(":3000", mux)}
timeHandler 函數現在有了個更巧妙的身份。除了把一個函數封裝成 Handler(像我們之前做到那樣),我們現在使用它來返回一個處理器。這種機制有兩個關鍵點:
首先是建立了一個fn,這是個匿名函數,將 format 變數封裝到一個閉包裡。閉包的本質讓處理器在任何情況下,都可以在本地範圍內訪問到 format 變數。
其次我們的閉包函數滿足 func(http.ResponseWriter, *http.Request) 簽名。如果你記得之前我們說的,這意味我們可以將它轉換成一個HandlerFunc類型(滿足了http.Handler介面)。我們的timeHandler 函數隨後轉換後的 HandlerFunc 返回。
在上面的例子中我們已經可以傳遞一個簡單的字串給處理器。但是在實際的應用中可以使用這種方法傳遞資料庫連接、模板組,或者其他應用級的上下文。使用全域變數也是個不錯的選擇,還能得到額外的好處就是編寫更優雅的自包含的處理器以便測試。
你也可能見過相同的寫法,像這樣:
func timeHandler(format string) http.Handler {return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(format)w.Write([]byte("The time is: " + tm))})}
或者在返回時,使用一個到 HandlerFunc 類型的隱式轉換:
func timeHandler(format string) http.HandlerFunc {return func(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(format)w.Write([]byte("The time is: " + tm))}}
更便利的 DefaultServeMux
你可能已經在很多地方看到過 DefaultServeMux, 從最簡單的 Hello World 例子,到 go 語言的原始碼中。
我花了很長時間才意識到 DefaultServerMux 並沒有什麼的特殊的地方。DefaultServerMux 就是我們之前用到的 ServerMux,只是它隨著 net/httpp 包初始化的時候被自動初始化了而已。Go 原始碼中的相關行如下:
var DefaultServeMux = NewServeMux()
net/http 包提供了一組捷徑來配合 DefaultServeMux:http.Handle 和 http.HandleFunc。這些函數與我們之前看過的類似的名稱的函數功能一樣,唯一的不同是他們將處理器註冊到 DefaultServerMux ,而之前我們是註冊到自己建立的 ServeMux。
此外,ListenAndServe在沒有提供其他的處理器的情況下(也就是第二個參數設成了 nil),內部會使用 DefaultServeMux。
因此,作為最後一個步驟,我們使用 DefaultServeMux 來改寫我們的 timeHandler應用:
//File: main.gopackage mainimport ("log""net/http""time")func timeHandler(format string) http.Handler {fn := func(w http.ResponseWriter, r *http.Request) {tm := time.Now().Format(format)w.Write([]byte("The time is: " + tm))}return http.HandlerFunc(fn)}func main() {// Note that we skip creating the ServeMux...var format string = time.RFC1123th := timeHandler(format)// We use http.Handle instead of mux.Handle...http.Handle("/time", th)log.Println("Listening...")// And pass nil as the handler to ListenAndServe.http.ListenAndServe(":3000", nil)}
======END======