鍍金池/ 教程/ GO/ 3.4 Go的http包詳解
7 文本處理
3 Web基礎(chǔ)
14 擴(kuò)展Web框架
10.4 小結(jié)
2.2 Go基礎(chǔ)
2.8 總結(jié)
6.1 session和cookie
5.5 使用beedb庫進(jìn)行ORM開發(fā)
8.3 REST
13.6 小結(jié)
5.4 使用PostgreSQL數(shù)據(jù)庫
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調(diào)試
7.7 小結(jié)
1 GO環(huán)境配置
14.5 多語言支持
7.1 XML處理
1.5 總結(jié)
13 如何設(shè)計(jì)一個Web框架
14.3 表單及驗(yàn)證支持
12 部署與維護(hù)
10 國際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數(shù)據(jù)庫操作
6.5 小結(jié)
9.4 避免SQL注入
12.1 應(yīng)用日志
4.2 驗(yàn)證表單的輸入
10.1 設(shè)置默認(rèn)地區(qū)
1.3 Go 命令
9.6 加密和解密數(shù)據(jù)
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測試用例
8 Web服務(wù)
12.3 應(yīng)用部署
5.7 小結(jié)
12.5 小結(jié)
11 錯誤處理,調(diào)試和測試
9.2 確保輸入過濾
14.2 Session支持
6.4 預(yù)防session劫持
12.4 備份和恢復(fù)
8.1 Socket編程
13.1 項(xiàng)目規(guī)劃
13.4 日志和配置設(shè)計(jì)
7.6 字符串處理
13.2 自定義路由器設(shè)計(jì)
6.3 session存儲
3.4 Go的http包詳解
8.2 WebSocket
10.3 國際化站點(diǎn)
7.5 文件操作
7.4 模板處理
9.1 預(yù)防CSRF攻擊
13.3 controller設(shè)計(jì)
2.6 interface
14.4 用戶認(rèn)證
2.3 流程和函數(shù)
附錄A 參考資料
11.1 錯誤處理
9.5 存儲密碼
9.3 避免XSS攻擊
12.2 網(wǎng)站錯誤處理
6 session和數(shù)據(jù)存儲
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向?qū)ο?/span>
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結(jié)
13.5 實(shí)現(xiàn)博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語言基礎(chǔ)
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結(jié)
4.3 預(yù)防跨站腳本
5.3 使用SQLite數(shù)據(jù)庫
14.7 小結(jié)
3.2 Go搭建一個Web服務(wù)器
2.7 并發(fā)
5 訪問數(shù)據(jù)庫
4 表單
3.5 小結(jié)
1.4 Go開發(fā)工具
11.4 小結(jié)
9 安全與加密
5.2 使用MySQL數(shù)據(jù)庫
4.6 小結(jié)
8.4 RPC

3.4 Go的http包詳解

前面小節(jié)介紹了Go怎么樣實(shí)現(xiàn)了Web工作模式的一個流程,這一小節(jié),我們將詳細(xì)地解剖一下http包,看它到底是怎樣實(shí)現(xiàn)整個過程的。

Go的http有兩個核心功能:Conn、ServeMux

Conn的goroutine

與我們一般編寫的http服務(wù)器不同, Go為了實(shí)現(xiàn)高并發(fā)和高性能, 使用了goroutines來處理Conn的讀寫事件, 這樣每個請求都能保持獨(dú)立,相互不會阻塞,可以高效的響應(yīng)網(wǎng)絡(luò)事件。這是Go高效的保證。

Go在等待客戶端請求里面是這樣寫的:

c, err := srv.newConn(rw)
if err != nil {
    continue
}
go c.serve()

這里我們可以看到客戶端的每次請求都會創(chuàng)建一個Conn,這個Conn里面保存了該次請求的信息,然后再傳遞到對應(yīng)的handler,該handler中便可以讀取到相應(yīng)的header信息,這樣保證了每個請求的獨(dú)立性。

ServeMux的自定義

我們前面小節(jié)講述conn.server的時(shí)候,其實(shí)內(nèi)部是調(diào)用了http包默認(rèn)的路由器,通過路由器把本次請求的信息傳遞到了后端的處理函數(shù)。那么這個路由器是怎么實(shí)現(xiàn)的呢?

它的結(jié)構(gòu)如下:

type ServeMux struct {
    mu sync.RWMutex   //鎖,由于請求涉及到并發(fā)處理,因此這里需要一個鎖機(jī)制
    m  map[string]muxEntry  // 路由規(guī)則,一個string對應(yīng)一個mux實(shí)體,這里的string就是注冊的路由表達(dá)式
    hosts bool // 是否在任意的規(guī)則中帶有host信息
}

下面看一下muxEntry

type muxEntry struct {
    explicit bool   // 是否精確匹配
    h        Handler // 這個路由表達(dá)式對應(yīng)哪個handler
    pattern  string  //匹配字符串
}

接著看一下Handler的定義

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  // 路由實(shí)現(xiàn)器
}

Handler是一個接口,但是前一小節(jié)中的sayhelloName函數(shù)并沒有實(shí)現(xiàn)ServeHTTP這個接口,為什么能添加呢?原來在http包里面還定義了一個類型HandlerFunc,我們定義的函數(shù)sayhelloName就是這個HandlerFunc調(diào)用之后的結(jié)果,這個類型默認(rèn)就實(shí)現(xiàn)了ServeHTTP這個接口,即我們調(diào)用了HandlerFunc(f),強(qiáng)制類型轉(zhuǎn)換f成為HandlerFunc類型,這樣f就擁有了ServeHTTP方法。

type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
    f(w, r)
}

路由器里面存儲好了相應(yīng)的路由規(guī)則之后,那么具體的請求又是怎么分發(fā)的呢?請看下面的代碼,默認(rèn)的路由器實(shí)現(xiàn)了ServeHTTP

func (mux *ServeMux) ServeHTTP(w ResponseWriter, r *Request) {
    if r.RequestURI == "*" {
        w.Header().Set("Connection", "close")
        w.WriteHeader(StatusBadRequest)
        return
    }
    h, _ := mux.Handler(r)
    h.ServeHTTP(w, r)
}

如上所示路由器接收到請求之后,如果是*那么關(guān)閉鏈接,不然調(diào)用mux.Handler(r)返回對應(yīng)設(shè)置路由的處理Handler,然后執(zhí)行h.ServeHTTP(w, r)

也就是調(diào)用對應(yīng)路由的handler的ServerHTTP接口,那么mux.Handler(r)怎么處理的呢?

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {
    if r.Method != "CONNECT" {
        if p := cleanPath(r.URL.Path); p != r.URL.Path {
            _, pattern = mux.handler(r.Host, p)
            return RedirectHandler(p, StatusMovedPermanently), pattern
        }
    }   
    return mux.handler(r.Host, r.URL.Path)
}

func (mux *ServeMux) handler(host, path string) (h Handler, pattern string) {
    mux.mu.RLock()
    defer mux.mu.RUnlock()

    // Host-specific pattern takes precedence over generic ones
    if mux.hosts {
        h, pattern = mux.match(host + path)
    }
    if h == nil {
        h, pattern = mux.match(path)
    }
    if h == nil {
        h, pattern = NotFoundHandler(), ""
    }
    return
}

原來他是根據(jù)用戶請求的URL和路由器里面存儲的map去匹配的,當(dāng)匹配到之后返回存儲的handler,調(diào)用這個handler的ServeHTTP接口就可以執(zhí)行到相應(yīng)的函數(shù)了。

通過上面這個介紹,我們了解了整個路由過程,Go其實(shí)支持外部實(shí)現(xiàn)的路由器 ListenAndServe的第二個參數(shù)就是用以配置外部路由器的,它是一個Handler接口,即外部路由器只要實(shí)現(xiàn)了Handler接口就可以,我們可以在自己實(shí)現(xiàn)的路由器的ServeHTTP里面實(shí)現(xiàn)自定義路由功能。

如下代碼所示,我們自己實(shí)現(xiàn)了一個簡易的路由器

package main

import (
    "fmt"
    "net/http"
)

type MyMux struct {
}

func (p *MyMux) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path == "/" {
        sayhelloName(w, r)
        return
    }
    http.NotFound(w, r)
    return
}

func sayhelloName(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello myroute!")
}

func main() {
    mux := &MyMux{}
    http.ListenAndServe(":9090", mux)
}

Go代碼的執(zhí)行流程

通過對http包的分析之后,現(xiàn)在讓我們來梳理一下整個的代碼執(zhí)行過程。

  • 首先調(diào)用Http.HandleFunc

    按順序做了幾件事:

    1 調(diào)用了DefaultServeMux的HandleFunc

    2 調(diào)用了DefaultServeMux的Handle

    3 往DefaultServeMux的map[string]muxEntry中增加對應(yīng)的handler和路由規(guī)則

  • 其次調(diào)用http.ListenAndServe(":9090", nil)

    按順序做了幾件事情:

    1 實(shí)例化Server

    2 調(diào)用Server的ListenAndServe()

    3 調(diào)用net.Listen("tcp", addr)監(jiān)聽端口

    4 啟動一個for循環(huán),在循環(huán)體中Accept請求

    5 對每個請求實(shí)例化一個Conn,并且開啟一個goroutine為這個請求進(jìn)行服務(wù)go c.serve()

    6 讀取每個請求的內(nèi)容w, err := c.readRequest()

    7 判斷handler是否為空,如果沒有設(shè)置handler(這個例子就沒有設(shè)置handler),handler就設(shè)置為DefaultServeMux

    8 調(diào)用handler的ServeHttp

    9 在這個例子中,下面就進(jìn)入到DefaultServeMux.ServeHttp

    10 根據(jù)request選擇handler,并且進(jìn)入到這個handler的ServeHTTP

      mux.handler(r).ServeHTTP(w, r)

    11 選擇handler:

    A 判斷是否有路由能滿足這個request(循環(huán)遍歷ServerMux的muxEntry)

    B 如果有路由滿足,調(diào)用這個路由handler的ServeHttp

    C 如果沒有路由滿足,調(diào)用NotFoundHandler的ServeHttp