golang實現(xiàn)簡易http服務(wù)器以及關(guān)鍵函數(shù)分析

簡易HTTP服務(wù)器的實現(xiàn)

先看一個使用net/http包實現(xiàn)的簡單服務(wù)器程序示例绰垂。

package main

import (
    "io"
    "log"
    "net/http"
)

type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "I'm home page")
}

func sayHello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "hello world!")
}

func main() {
    // 設(shè)置路由
    http.Handle("/", &myHandler{})
    http.HandleFunc("/hello", sayHello)
    err := http.ListenAndServe(":8080", nil)
    if err != nil {
        log.Fatal(err)
    }
}

運行程序,打開瀏覽器劲装,訪問http://localhost:8080,可以看到瀏覽器上顯示I'm home page绒怨,訪問http://localhost:8080/hello谦疾,可以看到hello world!。就是如此簡單餐蔬,但是程序背后做了哪些事呢?

分析

http.Handle主要是設(shè)置路由規(guī)則仗考,先看它的具體實現(xiàn)吧:

// Handle registers the handler for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func Handle(pattern string, handler Handler) { 
    DefaultServeMux.Handle(pattern, handler) 
}

可以看到兩個不認識的東西词爬,HandlerDefaultServeMux。繼續(xù)查看定義:

// Handler是一個接口
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

// ServeMux是一個自定義類型
// ServeMux also takes care of sanitizing the URL request path,
// redirecting any request containing . or .. elements or repeated slashes
// to an equivalent, cleaner URL.
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry //模式字符串與處理器的映射關(guān)系
    hosts bool // whether any patterns contain hostnames
}

// muxEntry 是一個自定義類型锅锨,包括一個模式字符串和對應(yīng)的Handler接口
type muxEntry struct {
    h       Handler
    pattern string
}

var defaultServeMux ServeMux
// DefaultServeMux is the default ServeMux used by Serve.
var DefaultServeMux = &defaultServeMux

DefaultServeMux*ServeMux類型恋沃,指向defaultServeMux這個內(nèi)部變量。ServeMux結(jié)構(gòu)體的含義是路由器囊咏,它的成員m比較重要,存儲的是模式字符串與處理器的映射關(guān)系霜第。*ServeMux實現(xiàn)了Handle函數(shù)户辞,主要用途就是填充成員m的,具體代碼如下:

// Handle registers the handler for the given pattern.
// If a handler already exists for pattern, Handle panics.
func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()
    // 錯誤判斷刃榨,模式字符串為空、處理起handler為空喇澡、對應(yīng)的模式字符串的處理器已經(jīng)存在
    if pattern == "" {
        panic("http: invalid pattern")
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if _, exist := mux.m[pattern]; exist {
        panic("http: multiple registrations for " + pattern)
    }

    // 如果沒有實例化,就make一下
    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    mux.m[pattern] = muxEntry{h: handler, pattern: pattern}

    if pattern[0] != '/' {
        mux.hosts = true
    }
}

可以看到前面有三個錯誤判斷读存,這個不影響理解呕屎,重要的是mux.m[pattern] = muxEntry{h: handler, pattern: pattern},將傳遞進來的處理器handler和模式字符串pattern組合成muxEntry結(jié)構(gòu)體秀睛,使用map結(jié)構(gòu)綁定patternmuxEntry之間的關(guān)系。

綜上所述椭迎,http.Handle干了什么呢田盈?主要就是操作DefaultServeMux這個結(jié)構(gòu)體變量,填充它的成員m(map[string]muxEntry類型)允瞧,建立路由規(guī)則。通俗一點痹升,就是為每個url模式串注冊了一個回調(diào)函數(shù)(go語言中稱之為處理器Handler)畦韭!

接下來的http.ListenAndServe函數(shù),具體實現(xiàn)如下:

// ListenAndServe always returns a non-nil error.
func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

這一段代碼不追究細節(jié)還是很好理解的据过,第一個參數(shù)是端口號,第二個參數(shù)是具體的處理器(該示例中傳入nil),這個函數(shù)就是先創(chuàng)建一個具體的Server對象西饵,然后調(diào)用ListenAndServe(),不看具體實現(xiàn)期虾,也能猜到,就是先listen(監(jiān)聽)镶苞,再建立一個事件循環(huán),監(jiān)聽到了連接事件或者讀寫事件發(fā)生壕鹉,就啟動一個協(xié)程進行處理聋涨。

問題是,傳遞進去的handlerServeHTTP方法再什么時候調(diào)用的呢牍白?繼續(xù)翻源碼,server.ListenAndServe()主要分為兩個步驟狸涌,1.處理新連接最岗,即調(diào)用Accpet函數(shù)。2.處理其他事件仑性,代碼中主要體現(xiàn)在go c.serve(ctx)。而在c.serve函數(shù)中找到了這句話:serverHandler{c.server}.ServeHTTP(w, w.req)诊杆。serverHandler類型其實就是重新包裝了*Server類型歼捐,如下:

// serverHandler delegates to either the server's Handler or
// DefaultServeMux and also handles "OPTIONS *" requests.
type serverHandler struct {
    srv *Server
}

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {
    // 重點, 取出內(nèi)部的Handler接口變量
    handler := sh.srv.Handler
    // 如果是空晨汹,就是用默認的Handler
    if handler == nil {
        handler = DefaultServeMux
    }
    if req.RequestURI == "*" && req.Method == "OPTIONS" {
        handler = globalOptionsHandler{}
    }
    // 調(diào)用 ServeHTTP 方法豹储!
    handler.ServeHTTP(rw, req)
}

serverHandlerServeHTTP方法中調(diào)用了它的成員Handler(最開始由你傳遞進來的)的ServeHTTP方法(這就是你自定義的處理器實現(xiàn)的ServeHTTP的作用所在了)。本示例中的傳入的Handlernil淘这,所以最后使用的是DefaultServeMuxServeHTTP方法剥扣,而DefaultServeMux*ServeMux類型,它的ServeHTTP方法實現(xiàn)如下:

// ServeHTTP dispatches the request to the handler whose
// pattern most closely matches the request URL.
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)
    h.ServeHTTP(w, r)
}

// handler is the main implementation of Handler.
// The path is known to be in canonical form, except for CONNECT methods.
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
}

mux.Handler函數(shù)中調(diào)用了mux.match函數(shù)铝穷,mux.match函數(shù)主要是在mux.m這個之前注冊好的map(字典)中查找url的對應(yīng)處理器。根據(jù)之前定義的路由規(guī)則找到了對應(yīng)的處理器之后曙聂,就調(diào)用處理器的ServeHTTP方法晦炊。

所以,在訪問http://localhost:8080時,執(zhí)行的是myHandler自定義類型的ServeHTTP方法断国,返回瀏覽器I'm home page字符串贤姆,并在瀏覽器上顯示出來。

http.HandleFunc的用法和http.Handle一樣稳衬,但是第二個參數(shù)是func(ResponseWriter, *Request)這個函數(shù)類型而不是Handler接口霞捡,由上面的分析可知,ServeMux的成員m存儲要求是個Handler接口薄疚,那么傳入個函數(shù)怎么處理呢碧信?見源碼:

// HandleFunc registers the handler function for the given pattern
// in the DefaultServeMux.
// The documentation for ServeMux explains how patterns are matched.
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

// The HandlerFunc type is an adapter to allow the use of
// ordinary functions as HTTP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

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

DefaultServeMux使用了HandleFunc方法,該方法將函數(shù)類型的handler強行轉(zhuǎn)換成了自定義類型HandlerFunc類型输涕,該類型實現(xiàn)了Handler接口音婶,也就使得傳入的函數(shù)也實現(xiàn)了Handler接口,在ServeHTTP方法中莱坎,直接調(diào)用自身代表的函數(shù)衣式。很是巧妙!

這些就是net/http包背后默默幫你做的事情檐什。

自定義路由器

下一個問題碴卧,如果不使用DefaultServeMux呢,能否自己定義一個ServeMux(路由器)乃正?答案是可以的住册,將簡單的HTTP服務(wù)器修改一下:

package main

import (
    "io"
    "log"
    "net/http"
)

type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "I'm home page")
}

func sayHello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "hello world")
}

func main() {
    mux := http.NewServeMux() // new出一個新的ServeMux對象

    mux.Handle("/", &myHandler{})
    mux.HandleFunc("/hello", sayHello)
    err := http.ListenAndServe(":8080", mux) //傳入自定義的路由器(mux)
    if err != nil {
        log.Fatal(err)
    }
}

與文章開頭不同的地方在于,使用http.NewServeMux()創(chuàng)建了一個ServeMux對象瓮具,然后調(diào)用mux.Handle注冊路由荧飞,最后在http.ListenAndServe中傳入自定義的路由器。

更進一步名党,能否不使用ServeMux結(jié)構(gòu)體呢叹阔?我們可以直接使用http.ListenAndServe中使用的Server結(jié)構(gòu),并且自己處理路由分發(fā)(自己定義字典存儲路由規(guī)則)传睹。

package main

import (
    "io"
    "log"
    "net/http"
    "time"
)

// 用字典實現(xiàn)路由器
var mux map[string]func(http.ResponseWriter, *http.Request)

// 自己實現(xiàn)的Handler
type myHandler struct{}

func (*myHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 根據(jù)url選擇對應(yīng)的處理函數(shù)
    if h, ok := mux[r.URL.String()]; ok {
        h(w, r)
        return
    }
    io.WriteString(w, "sorry, error happened!")
}

func sayHello(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "hello world")
}

func sayHome(w http.ResponseWriter, r *http.Request) {
    io.WriteString(w, "I'm home page")
}

func main() {
    server := http.Server{
        Addr:        ":8080",
        Handler:     &myHandler{},
        ReadTimeout: 5 * time.Second, //設(shè)置超時時間
    }

    mux = make(map[string]func(http.ResponseWriter, *http.Request))
    mux["/"] = sayHome
    mux["/hello"] = sayHello
    err := server.ListenAndServe()
    if err != nil {
        log.Fatal(err)
    }
}

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末耳幢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子欧啤,更是在濱河造成了極大的恐慌睛藻,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邢隧,死亡現(xiàn)場離奇詭異店印,居然都是意外死亡,警方通過查閱死者的電腦和手機倒慧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門按摘,熙熙樓的掌柜王于貴愁眉苦臉地迎上來讥邻,“玉大人,你說我怎么就攤上這事院峡。” “怎么了系宜?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵照激,是天一觀的道長。 經(jīng)常有香客問我盹牧,道長俩垃,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任汰寓,我火速辦了婚禮口柳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘有滑。我一直安慰自己跃闹,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布毛好。 她就那樣靜靜地躺著望艺,像睡著了一般。 火紅的嫁衣襯著肌膚如雪肌访。 梳的紋絲不亂的頭發(fā)上找默,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音吼驶,去河邊找鬼惩激。 笑死,一個胖子當(dāng)著我的面吹牛蟹演,可吹牛的內(nèi)容都是我干的风钻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼轨帜,長吁一口氣:“原來是場噩夢啊……” “哼魄咕!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蚌父,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤哮兰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后苟弛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體喝滞,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年膏秫,在試婚紗的時候發(fā)現(xiàn)自己被綠了右遭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖窘哈,靈堂內(nèi)的尸體忽然破棺而出吹榴,到底是詐尸還是另有隱情,我是刑警寧澤滚婉,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布图筹,位于F島的核電站,受9級特大地震影響让腹,放射性物質(zhì)發(fā)生泄漏远剩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一骇窍、第九天 我趴在偏房一處隱蔽的房頂上張望瓜晤。 院中可真熱鬧,春花似錦腹纳、人聲如沸痢掠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽志群。三九已至,卻和暖如春蛔钙,著一層夾襖步出監(jiān)牢的瞬間锌云,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工吁脱, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桑涎,地道東北人。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓兼贡,卻偏偏與公主長得像攻冷,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子遍希,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內(nèi)容

  • 【回顧盤面】: 10月24日(周二)兩市小幅低開凿蒜,之后滬指在持續(xù)橫盤整理至中午收盤禁谦,而創(chuàng)業(yè)板則一路單邊下行;午后滬...
    齊利閱讀 316評論 0 21
  • 默默中 我回味著那動人心眩的眼神 匆匆里 我追憶著那夢幻的眼神 執(zhí)著而堅定的眼神 給人一種頑強的信念 又似激情飛越...
    婉_霞閱讀 370評論 3 11
  • 【學(xué)習(xí)內(nèi)容】 1废封、閱讀《25歲州泊,規(guī)劃你的人生》50min (完) 2、參加我們產(chǎn)業(yè)園主辦的互聯(lián)網(wǎng)+時代人才戰(zhàn)略論壇...
    暖暖的魔羯麻麻閱讀 174評論 0 0