天天看點

Go語言備忘錄(3):net/http包的使用模式和源碼解析

本文是晚輩對net/http包的一點淺顯的了解,文中如有錯誤的地方請前輩們指出,以免誤導!

轉摘本文也請注明出處:

Go語言備忘錄(3):net/http包的使用模式和源碼解析

,多謝! 

目錄:

一、http包的3個關鍵類型:

Handler接口:所有請求的處理器、路由ServeMux都滿足該接口;

type

Handler 

interface

{

ServeHTTP(ResponseWriter, *Request)

}

ServeMux結構體:HTTP請求的多路轉接器(路由),它負責将每一個接收到的請求的URL與一個注冊模式的清單進行比對,并調用和URL最比對的模式的處理器。它内部用一個map來儲存所有處理器Handler

  • http包有一個包級别變量DefaultServeMux,表示預設路由:var DefaultServeMux = NewServeMux(),使用包級别的http.Handle()、http.HandleFunc()方法注冊處理器時都是注冊到該路由中;
  • ServeMux結構體有ServeHTTP()方法(滿足Handler接口),主要用于間接調用它所儲存的處理器的ServeHTTP()方法

http.HandlerFunc函數類型:它滿足Handler接口

type

HandlerFunc 

func

(ResponseWriter, *Request)

//實作Handler接口的ServeHTTP方法

func

(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {

f(w, r) 

//調用自身

}

二、HTTP伺服器的使用模式:

處理函數:隻要函數的簽名為 func(w http.ResponseWriter, r *http.Request) ,均可作為處理函數,即它可以被轉換為http.HandlerFunc函數類型;

模式一:使用預設的路由來注冊處理函數:

+ View Code

模式二:使用自定義的路由來注冊處理函數:

模式三:直接自定義一個Server執行個體:該模式可以很友善的管理服務端的行為

mux := http.NewServeMux()

mux.Handle(

"/file"

,myHandler(

"somefile"

))

mux.HandleFunc(

"/"

, serveHome)

s := &http.Server{

Addr: 

":8080"

,

Handler: mux, 

//指定路由或處理器,不指定時為nil,表示使用預設的路由DefaultServeMux

ReadTimeout: 10 * time.Second,

WriteTimeout: 10 * time.Second,

MaxHeaderBytes: 1 << 20,

ConnState: 

//指定連接配接conn的狀态改變時的處理函數

//....

}

log.Fatal(s.ListenAndServe())

接下來,我們就跟蹤源碼來仔細的分析下整個執行過程。

三、HTTP伺服器的執行過程:

1.使用http.ListenAndServe()方法啟動服務,它根據給定參數構造Server類型,然後調用server.ListenAndServe()

func

ListenAndServe(addr string, handler Handler) error {

server := &Server{Addr: addr, Handler: handler}

return

server.ListenAndServe()

}

  

2.而server.ListenAndServe()方法内部調用net.Listen("tcp", addr),該方法内部又調用net.ListenTCP()建立并傳回一個監聽器net.Listener,如下的ln;

func

(srv *Server) ListenAndServe() error {

addr := srv.Addr

if

addr == 

""

{

addr = 

":http"

}

ln, err := net.Listen(

"tcp"

, addr)

if

err != nil {

return

err

}

return

srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})

}

3.然後把監聽器 ln 斷言轉換為 TCPListener 類型,并根據它構造一個 tcpKeepAliveListener 對象并傳遞給server.Serve()方法;

  • 因為TCPListener實作了Listener接口,是以tcpKeepAliveListener也實作了Listener接口,并且它重寫了Accept()方法,目的是為了調用SetKeepAlive(true),讓作業系統為收到的每一個連接配接啟動發送keepalive消息(心跳,為了保持連接配接不斷開)。

type

tcpKeepAliveListener 

struct

{

*net.TCPListener

}

func

(ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {

tc, err := ln.AcceptTCP()

if

err != nil {

return

}

tc.SetKeepAlive(true) 

//發送心跳

tc.SetKeepAlivePeriod(3 * time.Minute) 

//發送周期

return

tc, nil

}

4.server.Serve()方法調用tcpKeepAliveListener 對象的 Accept() 方法傳回一個連接配接conn(該連接配接啟動了心跳),并為每一個conn建立一個新的go程執行conn.server()方法:具體見代碼中我加的注釋說明

5.而conn.server()方法會讀取請求,然後根據conn内儲存的server來構造一個serverHandler類型,并調用它的ServeHTTP()方法:serverHandler{c.server}.ServeHTTP(w, w.req),該方法的源碼如下:

func

(sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {

handler := sh.srv.Handler

if

handler == nil {

handler = DefaultServeMux

}

if

req.RequestURI == 

"*"

&& req.Method == 

"OPTIONS"

{

handler = globalOptionsHandler{}

}

handler.ServeHTTP(rw, req)

}

6.如上源碼可以看到,當 handler == nil 時使用預設的DefaultServeMux路由,否則使用在第1步中為Serve指定了的Handler;然後調用該Handler的ServeHTTP方法(該Handler一般被設定為路由ServeMux類型);

7.而路由ServeMux的ServeHTTP方法則會根據目前請求提供的資訊來查找最比對的Handler(這裡為):

func

(mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {

if

r.RequestURI == 

"*"

{

if

r.ProtoAtLeast(1, 1) {

w.Header().Set(

"Connection"

"close"

)

}

w.WriteHeader(StatusBadRequest)

return

}

h, _ := mux.Handler(r) 

//規範化請求的路徑格式,查找最比對的Handler

h.ServeHTTP(w, r)

}

8.以上查找到的Handler接口值h就是我們事先注冊到路由中與請求比對的Handler;而h的動态類型是HandlerFunc類型(它也滿足Handler接口);

是以,以上 h.ServeHTTP(w, r) 實際上調用的是接口值h中持有的動态值(也就是我們定義的處理函數)

type

HandlerFunc 

func

(ResponseWriter, *Request)

//實作Handler接口的ServeHTTP方法

func

(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {

f(w, r) 

//調用自身

}

至此,整個調用過程講解完畢,至于業務層的處理邏輯,則由各個處理函數實作

四、重定向:

http包自帶了幾個建立常用處理器的函數:FileServer,NotFoundHandler、RedirectHandler、StripPrefix、TimeoutHandler。

而RedirectHandler函數就是用來重定向的:它傳回一個請求處理器,該處理器會對每個請求都使用狀态碼code重定向到網址url

func

main() {

mux := http.NewServeMux()

mux.Handle(

"/to"

,http.RedirectHandler(

"http://example.org"

, 307))

err := http.ListenAndServe(*addr,mux) 

//啟動監聽

if

err != nil {

log.Fatalln(

"ListenAndServe: "

, err)

}

}

好了,本文就暫時講關于http包關于HTTP服務端方面的東西,至于用戶端方面的就簡單引用一下官方文檔說明吧,畢竟用戶端很少用Go實作。

五、用戶端的實作:

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"

}})

程式在使用完回複後必須關閉回複的主體。

resp, err := http.Get(

"http://example.com/"

)

if

err != nil {

// handle error

}

defer

resp.Body.Close()

body, err := ioutil.ReadAll(resp.Body)

// ...

要管理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類型都可以安全的被多個go程同時使用。出于效率考慮,應該一次建立、盡量重用。

以上如有誤導的地方,請前輩們務必指出!

讀完覺得學到點什麼,就( 頂一個!)