目錄golang
Go語言標準庫內建提供了net/http包,涵蓋了HTTP客戶端和服務端的具體實現。使用net/http包,咱們能夠很方便地編寫HTTP客戶端或服務端的程序。json
首先,咱們編寫一個最簡單的Web服務器。編寫這個Web服務只須要兩步:瀏覽器
註冊一個處理器函數(註冊到DefaultServeMux);安全
設置監聽的TCP地址並啓動服務;服務器
對應到咱們的代碼裏就是這樣的:app
package main import ( "fmt" "net/http" ) //say hello to the world func sayHello(w http.ResponseWriter, r *http.Request) { //n, err := fmt.Fprintln(w, "hello world") _, _ = w.Write([]byte("hello world")) } func main() { //1.註冊一個處理器函數 http.HandleFunc("/", sayHello) //2.設置監聽的TCP地址並啓動服務 //參數1:TCP地址(IP+Port) //參數2:handler handler參數通常會設爲nil,此時會使用DefaultServeMux。 err := http.ListenAndServe("127.0.0.1:9000", nil) if err != nil { fmt.Printf("http.ListenAndServe()函數執行錯誤,錯誤爲:%v\n", err) return } }
運行該程序,經過瀏覽器訪問,能夠看到hello world
顯示在了瀏覽器頁面上函數
ListenAndServe使用指定的監聽地址和處理器啓動一個HTTP服務端。處理器參數一般是nil,這表示採用包變量DefaultServeMux做爲處理器。post
Handle和HandleFunc函數能夠向DefaultServeMux添加處理器。網站
使用Go語言中的net/http
包來編寫一個簡單的接收HTTP請求的Server端示例,net/http
包是對net包的進一步封裝,專門用來處理HTTP協議的數據。具體的代碼以下:編碼
處理器函數的實現原理:
經過源碼可知,這個函數其實是調用了默認的serveMux的handleFunc方法, 這也解釋了咱們第一步裏所說的默認的實際註冊到DefaultServeMux
既然說了http.ListenAndServe
的第二個參數爲nil
時採用默認的DefaultServeMux,那麼若是咱們不想採用默認的,而是想本身建立一個ServerMux該怎麼辦呢,http給咱們提供了方法
func NewServeMux() *ServeMux
NewServeMux建立並返回一個新的*ServeMux
若是是咱們本身建立的ServeMux,咱們只須要簡單的更新一下代碼:
package main import ( "fmt" "net/http" ) //my goal is to become a gopher func myGoal(w http.ResponseWriter, r *http.Request) { _, _ = w.Write([]byte("I wan`t to become a gopher.")) } func main() { //1.註冊一個處理器函數 serveMux := http.NewServeMux() serveMux.HandleFunc("/", myGoal) //2.設置監聽的TCP地址並啓動服務 //參數1:TCP地址(IP+Port) //參數2:handler 建立新的*serveMux,不使用默認的 err := http.ListenAndServe("127.0.0.1:9000", serveMux) if err != nil { fmt.Printf("http.ListenAndServe()函數執行錯誤,錯誤爲:%v\n", err) return } }
運行修改後的代碼,和採用默認ServeMux同樣正常運行
若是是使用http的handle方法,則handle的第二個參數須要實現handler接口,要想實現這個接口,就得實現這個接口的serveHTTP
方法
package main import ( "fmt" "net/http" ) type MyHandler struct {} //實現Handler接口 func (h *MyHandler) ServeHTTP (w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "goodbye") } func main() { var handler MyHandler http.Handle("/sayGoodbye", &handler) var err = http.ListenAndServe(":8080", nil) if err != nil { fmt.Printf("http server failed, err: %v\n", err) return } }
一個Web服務器最基本的工做就是接收請求,作出響應。http包幫助咱們封裝了一個Request結構體,咱們經過這個結構體拿到不少用戶的一次HTTP請求的全部信息。這個Request結構體定義以下:
type Request struct { //Method指定HTTP方法(GET、POST、PUT等)。對客戶端,""表明GET。 Method string // 在客戶端,URL的Host字段指定了要鏈接的服務器, // 而Request的Host字段(可選地)指定要發送的HTTP請求的Host頭的值。 URL *url.URL //接收到的請求的協議版本。本包生產的Request使用HTTP/1.1或者HTTP/2 Proto string // "HTTP/1.0" ProtoMajor int // 1 ProtoMinor int // 0 //Header字段用來表示HTTP請求的頭域。 Header Header //請求主題 Body io.ReadCloser ..... }
我這裏列舉的並非完整的Request結構體定義,只是大體的說明一下。完整的定義以及這些字段的中文含義能夠查看Go語言標準庫中文文檔,不過須要注意的是因爲中文文檔更新不及時(畢竟非官方),會致使一些描述不許確,好比上面的Request結構體中的Proto請求協議版本字段在最新的Go版本中已經支持了HTTP/2,可是在其中的翻譯仍是停留在老版本的只支持HTTP/1.1。因此英語好的同窗仍是更推薦看源碼裏的官方文檔描述。
咱們經過經過瀏覽器能夠發現,咱們一次HTTP請求會攜帶不少信息
這些信息,咱們能夠用http.Request來獲取到
示例代碼:
package main import ( "fmt" "net/http" ) func myHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() fmt.Println("Method: ", r.Method) fmt.Println("URL: ", r.URL) fmt.Println("header: ", r.Header) fmt.Println("body: ", r.Body) fmt.Println("RemoteAddr: ", r.RemoteAddr) w.Write([]byte("請求成功!!!")) } func main() { http.HandleFunc("/", myHandler) err := http.ListenAndServe("127.0.0.1:9000", nil) if err != nil { fmt.Printf("http.ListenAndServe()函數執行錯誤,錯誤爲:%v\n", err) return } }
要管理服務端的行爲,能夠建立一個自定義的Server:
import ( "fmt" "net/http" "time" ) type MyHandler struct {} func (h *MyHandler) ServeHTTP (w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, "hello world!") } func main() { var handler MyHandler var server = http.Server{ Addr: ":8080", Handler: &handler, ReadTimeout: 2 * time.Second, MaxHeaderBytes: 1 << 20, } var err = server.ListenAndServe() if err != nil { fmt.Printf("http server failed, err: %v\n", err) return } }
http包提供了不少訪問Web服務器的函數,好比http.Get()
、http.Post()
、http.Head()
等,讀到的響應報文數據被保存在 Response 結構體中。
咱們能夠看一下Response結構體的定義
type Response struct { Status string // e.g. "200 OK" StatusCode int // e.g. 200 Proto string // e.g. "HTTP/1.0" ProtoMajor int // e.g. 1 ProtoMinor int // e.g. 0 Header Header Body io.ReadCloser //... }
上面只是Response的部分定義,完整的建議去查看源碼。
服務器發送的響應包體被保存在Body中。可使用它提供的Read方法來獲取數據內容。保存至切片緩衝區中,拼接成一個完整的字符串來查看。
結束的時候,須要調用Body中的Close()方法關閉io。
Get、Head、Post和PostForm函數發出HTTP/HTTPS請求。
resp, err := http.Get("http://example.com/") ... resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf) ... resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}})
程序在使用完response後必須關閉回覆的主體。
resp, err := http.Get("http://example.com/") if err != nil { // handle error } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) // ...
使用net/http
包編寫一個簡單的發送HTTP請求的Client端,代碼以下:
package main import ( "fmt" "io/ioutil" "net/http" ) func main() { resp, err := http.Get("http://127.0.0.1:9000") if err != nil { fmt.Printf("http.Get()函數執行錯誤,錯誤爲:%v\n", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Printf("ioutil.ReadAll()函數執行出錯,錯誤爲:%v\n", err) return } fmt.Println(string(body)) }
將上面的代碼保存以後編譯成可執行文件,執行以後就能在終端打印請求成功!!!
網站首頁的內容了,咱們的瀏覽器其實就是一個發送和接收HTTP協議數據的客戶端,咱們平時經過瀏覽器訪問網頁其實就是從網站的服務器接收HTTP數據,而後瀏覽器會按照HTML、CSS等規則將網頁渲染展現出來。
關於GET請求的參數須要使用Go語言內置的net/url
這個標準庫來處理。
package main import ( "fmt" "io/ioutil" "net/http" "net/url" ) func main() { //1.處理請求參數 params := url.Values{} params.Set("name", "itbsl") params.Set("hobby", "fishing") //2.設置請求URL rawUrl := "http://127.0.0.1:9000" reqURL, err := url.ParseRequestURI(rawUrl) if err != nil { fmt.Printf("url.ParseRequestURI()函數執行錯誤,錯誤爲:%v\n", err) return } //3.整合請求URL和參數 //Encode方法將請求參數編碼爲url編碼格式("bar=baz&foo=quux"),編碼時會以鍵進行排序。 reqURL.RawQuery = params.Encode() //4.發送HTTP請求 //說明: reqURL.String() String將URL重構爲一個合法URL字符串。 resp, err := http.Get(reqURL.String()) if err != nil { fmt.Printf("http.Get()函數執行錯誤,錯誤爲:%v\n", err) return } defer resp.Body.Close() //5.一次性讀取響應的全部內容 body, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Printf("ioutil.ReadAll()函數執行出錯,錯誤爲:%v\n", err) return } fmt.Println(string(body)) }
對應的Server端代碼以下:
package main import ( "fmt" "net/http" ) func myHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() params := r.URL.Query() fmt.Fprintln(w, "name:", params.Get("name"), "hobby:", params.Get("hobby")) } func main() { http.HandleFunc("/", myHandler) err := http.ListenAndServe("127.0.0.1:9000", nil) if err != nil { fmt.Printf("http.ListenAndServe()函數執行錯誤,錯誤爲:%v\n", err) return } }
上面演示了使用net/http
包發送GET
請求的示例,發送POST
請求的示例代碼以下:
package main import ( "fmt" "io/ioutil" "net/http" "strings" ) // net/http post demo func main() { url := "http://127.0.0.1:9090/post" // 表單數據 //contentType := "application/x-www-form-urlencoded" //data := "name=小王子&age=18" // json contentType := "application/json" data := `{"name":"小王子","age":18}` resp, err := http.Post(url, contentType, strings.NewReader(data)) if err != nil { fmt.Println("post failed, err:%v\n", err) return } defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) if err != nil { fmt.Println("get resp failed,err:%v\n", err) return } fmt.Println(string(b)) }
對應的Server端HandlerFunc以下:
func postHandler(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() // 1. 請求類型是application/x-www-form-urlencoded時解析form數據 r.ParseForm() fmt.Println(r.PostForm) // 打印form數據 fmt.Println(r.PostForm.Get("name"), r.PostForm.Get("age")) // 2. 請求類型是application/json時從r.Body讀取數據 b, err := ioutil.ReadAll(r.Body) if err != nil { fmt.Println("read request.Body failed, err:%v\n", err) return } fmt.Println(string(b)) answer := `{"status": "ok"}` w.Write([]byte(answer)) }
要管理HTTP客戶端的頭域、重定向策略和其餘設置,建立一個Client:
client := &http.Client{ CheckRedirect: redirectPolicyFunc, } resp, err := client.Get("http://example.com") // ... req, err := http.NewRequest("GET", "http://example.com", nil) // ... req.Header.Add("If-None-Match", `W/"wyzzy"`) resp, err := client.Do(req) // ...
要管理代理、TLS配置、keep-alive、壓縮和其餘設置,建立一個Transport:
tr := &http.Transport{ TLSClientConfig: &tls.Config{RootCAs: pool}, DisableCompression: true, } client := &http.Client{Transport: tr} resp, err := client.Get("https://example.com")
Client和Transport類型均可以安全的被多個goroutine同時使用。出於效率考慮,應該一次創建、儘可能重用。