Go的web工作原理

在Go中使用及其簡單的代碼即可開啟一個web服務闹蒜。如下:

//開啟web服務
func test(){
    http.HandleFunc("/", sayHello)
    err := http.ListenAndServe(":9090",nil)
    if err!=nil {
        log.Fatal("ListenAndServer:",err)
    }
}

func sayHello(w http.ResponseWriter, r *http.Request){
    r.ParseForm()
    fmt.Println("path",r.URL.Path)
    fmt.Println("scheme",r.URL.Scheme)

    fmt.Fprintf(w, "Hello Guest!")
}

在使用ListenAndServe這個方法時肋僧,系統(tǒng)就會給我們指派一個路由器羞秤,DefaultServeMux是系統(tǒng)默認使用的路由器来候,如果ListenAndServe這個方法的第2個參數(shù)傳入nil跷叉,系統(tǒng)就會默認使用DefaultServeMux。當然营搅,這里也可以傳入自定義的路由器云挟。

先來看http.HandleFunc("/", sayHello),從HandleFunc方法點進去转质,如下:

func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    DefaultServeMux.HandleFunc(pattern, handler)
}

在這里調(diào)用了DefaultServeMuxHandleFunc方法园欣,這個方法有兩個參數(shù),pattern是匹配的路由規(guī)則休蟹,handler表示這個路由規(guī)則對應的處理方法沸枯,并且這個處理方法有兩個參數(shù)。

在我們書寫的代碼示例中赂弓,pattern對應/绑榴,handler對應sayHello,當我們在瀏覽器中輸入http://localhost:9090時盈魁,就會觸發(fā)sayHello方法翔怎。

我們再順著DefaultServeMuxHandleFunc方法繼續(xù)點下去,如下:

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    mux.Handle(pattern, HandlerFunc(handler))
}

在這個方法中杨耙,路由器又調(diào)用了Handle方法赤套,注意這個Handle方法的第2個參數(shù),將之前傳入的handler這個響應方法強制轉(zhuǎn)換成了HandlerFunc類型珊膜。

這個HandlerFunc類型到底是個什么呢容握?如下:

type HandlerFunc func(ResponseWriter, *Request)

看來和我們定義的SayHello方法的類型都差不多。但是3的Nň凇脖旱!
這個HandlerFunc默認實現(xiàn)了ServeHTTP接口!這樣HandlerFunc對象就有了ServeHTTP方法介蛉!如下:

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

這個細節(jié)是十分重要的萌庆,因為這一步關(guān)乎到當路由規(guī)則匹配時,相應的響應方法是否會被調(diào)用的問題币旧!這個方法的調(diào)用時機會在下一小節(jié)中講到践险。

接下來,我們返回去繼續(xù)看muxHandle方法吹菱,也就是這段代碼mux.Handle(pattern, HandlerFunc(handler))巍虫。這段代碼做了哪些事呢?源碼如下:

func (mux *ServeMux) Handle(pattern string, handler Handler) {
    mux.mu.Lock()
    defer mux.mu.Unlock()

    if pattern == "" {
        panic("http: invalid pattern " + pattern)
    }
    if handler == nil {
        panic("http: nil handler")
    }
    if mux.m[pattern].explicit {
        panic("http: multiple registrations for " + pattern)
    }

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}

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

    // Helpful behavior:
    // If pattern is /tree/, insert an implicit permanent redirect for /tree.
    // It can be overridden by an explicit registration.
    n := len(pattern)
    if n > 0 && pattern[n-1] == '/' && !mux.m[pattern[0:n-1]].explicit {
        // If pattern contains a host name, strip it and use remaining
        // path for redirect.
        path := pattern
        if pattern[0] != '/' {
            // In pattern, at least the last character is a '/', so
            // strings.Index can't be -1.
            path = pattern[strings.Index(pattern, "/"):]
        }
        url := &url.URL{Path: path}
        mux.m[pattern[0:n-1]] = muxEntry{h: RedirectHandler(url.String(), StatusMovedPermanently), pattern: pattern}
    }
}

代碼挺多鳍刷,其實主要就做了一件事占遥,向DefaultServeMuxmap[string]muxEntry中增加對應的路由規(guī)則和handler

map[string]muxEntry是個什么鬼输瓜?

map是一個字典對象瓦胎,它保存的是key-value
[string]表示這個字典的keystring類型的尤揣,這個key值會保存我們的路由規(guī)則搔啊。
muxEntry是一個實例對象,這個對象內(nèi)保存了路由規(guī)則對應的處理方法北戏。

找到相應代碼负芋,如下:

//路由器
type ServeMux struct {
    mu    sync.RWMutex
    m     map[string]muxEntry //路由規(guī)則,一個string對應一個mux實例對象嗜愈,map的key就是注冊的路由表達式(string類型的)
    hosts bool // whether any patterns contain hostnames
}

//muxEntry
type muxEntry struct {
    explicit bool
    h        Handler //這個路由表達式對應哪個handler
    pattern  string
}

//路由響應方法
type Handler interface {
    ServeHTTP(ResponseWriter, *Request)  //handler的路由實現(xiàn)器
}

ServeMux就是這個系統(tǒng)默認的路由器旧蛾。

最后,總結(jié)一下這個部分:
1.調(diào)用http.HandleFunc("/", sayHello)
2.調(diào)用DefaultServeMuxHandleFunc()蠕嫁,把我們定義的sayHello()包裝成HandlerFunc類型
3.繼續(xù)調(diào)用DefaultServeMuxHandle()锨天,向DefaultServeMuxmap[string]muxEntry中增加路由規(guī)則和對應的handler

OK,這部分代碼做的事就這么多拌阴,第一部分結(jié)束。


第二部分主要就是研究這句代碼err := http.ListenAndServe(":9090",nil)奶镶,也就是ListenAndServe這個方法迟赃。從這個方法點進去,如下:

func ListenAndServe(addr string, handler Handler) error {
    server := &Server{Addr: addr, Handler: handler}
    return server.ListenAndServe()
}

在這個方法中厂镇,初始化了一個server對象纤壁,然后調(diào)用這個server對象的ListenAndServe方法,在這個方法中捺信,如下:

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)})
}

在這個方法中酌媒,調(diào)用了net.Listen("tcp", addr)欠痴,也就是底層用TCP協(xié)議搭建了一個服務,然后監(jiān)控我們設置的端口秒咨。

代碼的最后喇辽,調(diào)用了srvServe方法,如下:

func (srv *Server) Serve(l net.Listener) error {
    defer l.Close()
    if fn := testHookServerServe; fn != nil {
        fn(srv, l)
    }
    var tempDelay time.Duration // how long to sleep on accept failure

    if err := srv.setupHTTP2_Serve(); err != nil {
        return err
    }

    srv.trackListener(l, true)
    defer srv.trackListener(l, false)

    baseCtx := context.Background() // base is always background, per Issue 16220
    ctx := context.WithValue(baseCtx, ServerContextKey, srv)
    ctx = context.WithValue(ctx, LocalAddrContextKey, l.Addr())
    for {
        rw, e := l.Accept()
        if e != nil {
            select {
            case <-srv.getDoneChan():
                return ErrServerClosed
            default:
            }
            if ne, ok := e.(net.Error); ok && ne.Temporary() {
                if tempDelay == 0 {
                    tempDelay = 5 * time.Millisecond
                } else {
                    tempDelay *= 2
                }
                if max := 1 * time.Second; tempDelay > max {
                    tempDelay = max
                }
                srv.logf("http: Accept error: %v; retrying in %v", e, tempDelay)
                time.Sleep(tempDelay)
                continue
            }
            return e
        }
        tempDelay = 0
        c := srv.newConn(rw)
        c.setState(c.rwc, StateNew) // before Serve can return
        go c.serve(ctx)
    }
}

最后3段代碼比較重要雨席,也是Go語言支持高并發(fā)的體現(xiàn)菩咨,如下:

c := srv.newConn(rw)
c.setState(c.rwc, StateNew) // before Serve can return
go c.serve(ctx)

上面那一大坨代碼,總體意思是進入方法后陡厘,首先開了一個for循環(huán)抽米,在for循環(huán)內(nèi)時刻Accept請求,請求來了之后糙置,會為每個請求創(chuàng)建一個Conn云茸,然后單獨開啟一個goroutine,把這個請求的數(shù)據(jù)當做參數(shù)扔給這個Conn去服務:go c.serve()谤饭。用戶的每一次請求都是在一個新的goroutine去服務标捺,每個請求間相互不影響。

connserve方法中网持,有一句代碼很重要宜岛,如下:

serverHandler{c.server}.ServeHTTP(w, w.req)

表示serverHandler實現(xiàn)了ServeHTTP接口,ServeHTTP方法實現(xiàn)如下:

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)
}

在這里如果handler為空(這個handler就可以理解為是我們自定義的路由器)功舀,就會使用系統(tǒng)默認的DefaultServeMux萍倡,代碼的最后調(diào)用了DefaultServeMuxServeHTTP()

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是Handler接口對象
    h.ServeHTTP(w, r)       //調(diào)用Handler接口對象的ServeHTTP方法實際上就調(diào)用了我們定義的sayHello方法
}

路由器接收到請求之后,如果是*那么關(guān)閉鏈接辟汰,如果不是*就調(diào)用mux.Handler(r)返回該路由對應的處理Handler列敲,然后執(zhí)行該handlerServeHTTP方法,也就是這句代碼h.ServeHTTP(w, r)帖汞,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)
            url := *r.URL
            url.Path = p
            return RedirectHandler(url.String(), 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
}

func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    var n = 0
    for k, v := range mux.m {  //mux.m就是系統(tǒng)默認路由的map
        if !pathMatch(k, path) {
            continue
        }
        if h == nil || len(k) > n {
            n = len(k)
            h = v.h
            pattern = v.pattern
        }
    }
    return
}

它會根據(jù)用戶請求的URL到路由器里面存儲的map中匹配,匹配成功就會返回存儲的handler翩蘸,調(diào)用這個handlerServeHTTP()就可以執(zhí)行到相應的處理方法了所意,這個處理方法實際上就是我們剛開始定義的sayHello(),只不過這個sayHello()HandlerFunc又包了一層催首,因為HandlerFunc實現(xiàn)了ServeHTTP接口扶踊,所以在調(diào)用HandlerFunc對象的ServeHTTP()時,實際上在ServeHTTP ()的內(nèi)部調(diào)用了我們的sayHello()郎任。

總結(jié)一下:
1.調(diào)用http.ListenAndServe(":9090",nil)
2.實例化server
3.調(diào)用serverListenAndServe()
4.調(diào)用serverServe方法秧耗,開啟for循環(huán),在循環(huán)中Accept請求
5.對每一個請求實例化一個Conn舶治,并且開啟一個goroutine為這個請求進行服務go c.serve()
6.讀取每個請求的內(nèi)容c.readRequest()
7.調(diào)用serverHandlerServeHTTP()分井,如果handler為空车猬,就把handler設置為系統(tǒng)默認的路由器DefaultServeMux
8.調(diào)用handlerServeHTTP() =>實際上是調(diào)用了DefaultServeMuxServeHTTP()
9.在ServeHTTP()中會調(diào)用路由對應處理handler
10.在路由對應處理handler中會執(zhí)行sayHello()


有一個需要注意的點:
DefaultServeMux和路由對應的處理方法handler都實現(xiàn)了ServeHTTP接口,他們倆都有ServeHTTP方法尺锚,但是方法要達到的目的不同珠闰,在DefaultServeMuxServeHttp()里會執(zhí)行路由對應的處理handlerServeHttp()

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末缩麸,一起剝皮案震驚了整個濱河市铸磅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌杭朱,老刑警劉巖阅仔,帶你破解...
    沈念sama閱讀 218,284評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異弧械,居然都是意外死亡八酒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評論 3 395
  • 文/潘曉璐 我一進店門刃唐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羞迷,“玉大人,你說我怎么就攤上這事画饥∠挝停” “怎么了?”我有些...
    開封第一講書人閱讀 164,614評論 0 354
  • 文/不壞的土叔 我叫張陵抖甘,是天一觀的道長热鞍。 經(jīng)常有香客問我,道長衔彻,這世上最難降的妖魔是什么薇宠? 我笑而不...
    開封第一講書人閱讀 58,671評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮艰额,結(jié)果婚禮上澄港,老公的妹妹穿的比我還像新娘。我一直安慰自己柄沮,他們只是感情好回梧,可當我...
    茶點故事閱讀 67,699評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著祖搓,像睡著了一般狱意。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上棕硫,一...
    開封第一講書人閱讀 51,562評論 1 305
  • 那天髓涯,我揣著相機與錄音袒啼,去河邊找鬼哈扮。 笑死纬纪,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的滑肉。 我是一名探鬼主播包各,決...
    沈念sama閱讀 40,309評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼靶庙!你這毒婦竟也來了问畅?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,223評論 0 276
  • 序言:老撾萬榮一對情侶失蹤六荒,失蹤者是張志新(化名)和其女友劉穎护姆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體掏击,經(jīng)...
    沈念sama閱讀 45,668評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡卵皂,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,859評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了砚亭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片灯变。...
    茶點故事閱讀 39,981評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖捅膘,靈堂內(nèi)的尸體忽然破棺而出添祸,到底是詐尸還是另有隱情,我是刑警寧澤寻仗,帶...
    沈念sama閱讀 35,705評論 5 347
  • 正文 年R本政府宣布刃泌,位于F島的核電站,受9級特大地震影響愧沟,放射性物質(zhì)發(fā)生泄漏蔬咬。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,310評論 3 330
  • 文/蒙蒙 一沐寺、第九天 我趴在偏房一處隱蔽的房頂上張望林艘。 院中可真熱鬧,春花似錦混坞、人聲如沸狐援。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,904評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽啥酱。三九已至,卻和暖如春厨诸,著一層夾襖步出監(jiān)牢的瞬間镶殷,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,023評論 1 270
  • 我被黑心中介騙來泰國打工微酬, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绘趋,地道東北人颤陶。 一個月前我還...
    沈念sama閱讀 48,146評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像陷遮,于是被迫代替她去往敵國和親滓走。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,933評論 2 355

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,139評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理帽馋,服務發(fā)現(xiàn)搅方,斷路器,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 22年12月更新:個人網(wǎng)站關(guān)停绽族,如果仍舊對舊教程有興趣參考 Github 的markdown內(nèi)容[https://...
    tangyefei閱讀 35,182評論 22 257
  • 教學管理五字訣 一一讀《大清相國》所想 那年暑假我鬼使神差地迷上了王躍文的書:《西州月》《國畫》《大清相國》姨涡。前...
    紅巧兒閱讀 669評論 0 2
  • 春有百花秋有月绣溜,夏有涼風冬有雪 。 鹵貓不是貓娄蔼,更不是什么虐待小動物的行為怖喻,鹵貓是一個80后小哥的名字。 他是一名...
    CinoStar閱讀 801評論 0 2