天天看點

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

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

目錄:

<a href="http://www.cnblogs.com/susufufu/p/7698900.html#t1">一、http包的3個關鍵類型</a>

<a href="http://www.cnblogs.com/susufufu/p/7698900.html#t2">二、HTTP伺服器的使用模式</a>

<a href="http://www.cnblogs.com/susufufu/p/7698900.html#t3">三、HTTP伺服器的執行過程</a>

<a href="http://www.cnblogs.com/susufufu/p/7698900.html#t4">四、重定向</a>

<a href="http://www.cnblogs.com/susufufu/p/7698900.html#t5">五、用戶端的實作</a>

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

<code>type</code> <code>Handler </code><code>interface</code> <code>{</code>

<code>   </code><code>ServeHTTP(ResponseWriter, *Request)</code>

<code>}</code>

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

http包有一個包級别變量DefaultServeMux,表示預設路由:var DefaultServeMux = NewServeMux(),使用包級别的http.Handle()、http.HandleFunc()方法注冊處理器時都是注冊到該路由中;

ServeMux結構體有ServeHTTP()方法(滿足Handler接口),主要用于間接調用它所儲存的處理器的ServeHTTP()方法

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

<code>type</code> <code>HandlerFunc </code><code>func</code><code>(ResponseWriter, *Request)</code>

<code>//實作Handler接口的ServeHTTP方法</code>

<code>func</code> <code>(f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {</code>

<code>    </code><code>f(w, r) </code><code>//調用自身</code>

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

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

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

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

<code>mux := http.NewServeMux()</code>

<code>mux.Handle(</code><code>"/file"</code><code>,myHandler(</code><code>"somefile"</code><code>))</code>

<code>mux.HandleFunc(</code><code>"/"</code><code>, serveHome)</code>

<code>s := &amp;http.Server{</code>

<code>   </code><code>Addr: </code><code>":8080"</code><code>,</code>

<code>   </code><code>Handler: mux, </code><code>//指定路由或處理器,不指定時為nil,表示使用預設的路由DefaultServeMux</code>

<code>   </code><code>ReadTimeout: 10 * time.Second,</code>

<code>   </code><code>WriteTimeout: 10 * time.Second,</code>

<code>   </code><code>MaxHeaderBytes: 1 &lt;&lt; 20,</code>

<code>   </code><code>ConnState: </code><code>//指定連接配接conn的狀态改變時的處理函數</code>

<code>       </code><code>//....</code>

<code>log.Fatal(s.ListenAndServe())</code>

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

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

<code>func</code> <code>ListenAndServe(addr string, handler Handler) error {</code>

<code>    </code><code>server := &amp;Server{Addr: addr, Handler: handler}</code>

<code>    </code><code>return</code> <code>server.ListenAndServe()</code>

  

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

<code>func</code> <code>(srv *Server) ListenAndServe() error {</code>

<code>    </code><code>addr := srv.Addr</code>

<code>    </code><code>if</code> <code>addr == </code><code>""</code> <code>{</code>

<code>        </code><code>addr = </code><code>":http"</code>

<code>    </code><code>}</code>

<code>    </code><code>ln, err := net.Listen(</code><code>"tcp"</code><code>, addr)</code>

<code>    </code><code>if</code> <code>err != nil {</code>

<code>        </code><code>return</code> <code>err</code>

<code>    </code><code>return</code> <code>srv.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)})</code>

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

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

<code>type</code> <code>tcpKeepAliveListener </code><code>struct</code> <code>{</code>

<code>    </code><code>*net.TCPListener</code>

<code>func</code> <code>(ln tcpKeepAliveListener) Accept() (c net.Conn, err error) {</code>

<code>    </code><code>tc, err := ln.AcceptTCP()</code>

<code>        </code><code>return</code>

<code>    </code><code>tc.SetKeepAlive(true) </code><code>//發送心跳</code>

<code>    </code><code>tc.SetKeepAlivePeriod(3 * time.Minute) </code><code>//發送周期</code>

<code>    </code><code>return</code> <code>tc, nil</code>

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

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

<code>func</code> <code>(sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {</code>

<code>   </code><code>handler := sh.srv.Handler</code>

<code>   </code><code>if</code> <code>handler == nil {</code>

<code>      </code><code>handler = DefaultServeMux</code>

<code>   </code><code>}</code>

<code>   </code><code>if</code> <code>req.RequestURI == </code><code>"*"</code> <code>&amp;&amp; req.Method == </code><code>"OPTIONS"</code> <code>{</code>

<code>      </code><code>handler = globalOptionsHandler{}</code>

<code>   </code><code>handler.ServeHTTP(rw, req)</code>

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

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

<code>func</code> <code>(mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {</code>

<code>    </code><code>if</code> <code>r.RequestURI == </code><code>"*"</code> <code>{</code>

<code>        </code><code>if</code> <code>r.ProtoAtLeast(1, 1) {</code>

<code>            </code><code>w.Header().Set(</code><code>"Connection"</code><code>, </code><code>"close"</code><code>)</code>

<code>        </code><code>}</code>

<code>        </code><code>w.WriteHeader(StatusBadRequest)</code>

<code>    </code><code>h, _ := mux.Handler(r) </code><code>//規範化請求的路徑格式,查找最比對的Handler</code>

<code>    </code><code>h.ServeHTTP(w, r)</code>

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

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

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

<a></a>

四、重定向:

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

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

<code>func</code> <code>main() {</code>

<code>  </code><code>mux := http.NewServeMux()</code>

<code>  </code><code>mux.Handle(</code><code>"/to"</code><code>,http.RedirectHandler(</code><code>"http://example.org"</code><code>, 307))</code>

<code>  </code><code>err := http.ListenAndServe(*addr,mux) </code><code>//啟動監聽</code>

<code>   </code><code>if</code> <code>err != nil {</code>

<code>      </code><code>log.Fatalln(</code><code>"ListenAndServe: "</code><code>, err)</code>

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

五、用戶端的實作:

Get、Head、Post和PostForm函數發出HTTP/ HTTPS請求。

<code>resp, err := http.Get(</code><code>"http://example.com/"</code><code>)</code>

<code>...</code>

<code>resp, err := http.Post(</code><code>"http://example.com/upload"</code><code>, </code><code>"image/jpeg"</code><code>, &amp;buf)</code>

<code>resp, err := http.PostForm(</code><code>"http://example.com/form"</code><code>,</code>

<code>    </code><code>url.Values{</code><code>"key"</code><code>: {</code><code>"Value"</code><code>}, </code><code>"id"</code><code>: {</code><code>"123"</code><code>}})</code>

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

<code>if</code> <code>err != nil {</code>

<code>    </code><code>// handle error</code>

<code>defer</code> <code>resp.Body.Close()</code>

<code>body, err := ioutil.ReadAll(resp.Body)</code>

<code>// ...</code>

要管理HTTP用戶端的頭域、重定向政策和其他設定,建立一個Client:

<code>client := &amp;http.Client{</code>

<code>    </code><code>CheckRedirect: redirectPolicyFunc,</code>

<code>resp, err := client.Get(</code><code>"http://example.com"</code><code>)</code>

<code>req, err := http.NewRequest(</code><code>"GET"</code><code>, </code><code>"http://example.com"</code><code>, nil)</code>

<code>req.Header.Add(</code><code>"If-None-Match"</code><code>, `W/</code><code>"wyzzy"</code><code>`)</code>

<code>resp, err := client.Do(req)</code>

要管理代理、TLS配置、keep-alive、壓縮和其他設定,建立一個Transport:

<code>tr := &amp;http.Transport{</code>

<code>    </code><code>TLSClientConfig:    &amp;tls.Config{RootCAs: pool},</code>

<code>    </code><code>DisableCompression: true,</code>

<code>client := &amp;http.Client{Transport: tr}</code>

<code>resp, err := client.Get(</code><code>"https://example.com"</code><code>)</code>

Client和Transport類型都可以安全的被多個go程同時使用。出于效率考慮,應該一次建立、盡量重用。

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