[Introduction]萬字手撕Go http源碼server.go

Author:Wzy_CChtml

全文一共10000字git

閱讀時間10~15分鐘程序員

前言

本文目標:github

從路由註冊到監聽本地端口後請求路由的一系列動做的分析,基本上僅限於net/http server.go這個包文件的路由相關部分解讀golang

寫做目的:web

在使用原生庫進行web開發的時候,不少初學者很容易被mux.Handle()/mux.HandleFunc()/mux.Handler()/Handlerfunc/Handler/Handle()/Handlefunc()/handler給唬住,自己幾個名稱就相近,首字母有時候大寫有時候小寫,有時候是handle,有時候是handler,看起來類似可是類型和做用卻徹底不一樣。由於命名類似容易混淆,所以其真實含義也不容易搞清楚,對於開發者來講也不容易記憶 。有些命名甚至看不出來這個函數究竟是幹什麼用的,有些屬於設計庫的時候的歷史遺留問題,使得理解http庫變得更加困難。segmentfault

不少網上的教程只是講了某些東西是什麼,用來幹什麼的,而沒有講爲何是這樣的,爲何要這樣設計,這樣設計有什麼好處。更重要的是有些教程已經老了,2018年到如今已經兩年了,不少函數都通過優化重寫了,至少server.go中的不少函數變化都很大,2018年的教程不少已通過時了,可能2022年又須要從新寫一篇http庫的解讀。不過有些東西是不變的,不少設計思想都是共通的,而這些思想纔是初學者應該掌握的。事實上死記硬背掌握handle的四種寫法對開發沒有任何幫助,若是不深刻理解之後還會常常性的把文檔翻來翻去而一頭霧水。設計模式

Go的有些設計哲學頗有趣,不是簡簡單單幾萬字的篇幅就能夠講明白的。數組

閱讀順序:服務器

本文按照順序閱讀會比較有幫助

目錄

[TOC]

概述

官網示例

在官網示例中,使用go搭建一個穩定的高併發web服務器僅須要短短几行:

http.Handle("/foo", fooHandler)

http.HandleFunc("/bar", func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello, %q", html.EscapeString(r.URL.Path))
})

log.Fatal(http.ListenAndServe(":8080", nil))

固然對於大部分開發來講,這幾行代碼已經足夠在生產環境中使用了,可是若是對比較底層的一些原理不解,那麼還須要繼續深究。

一個go服務器正常運行起來的步驟大體有:註冊函數、監聽端口,接受請求,處理請求,提供服務,關閉連接

0.註冊函數:首先往路由表中註冊對應的路由規則

1.監聽端口:建立listen socket,循環監聽

2.接受請求:接受請求,建立網絡連接conn對象,開啓一個協程處理該連接(估計多路複用複用在這裏了)每服務一個新的連接,在conn.connect()中就會調用serveHTTP來處理請求

3.處理請求:讀取請求參數構造Request對象,根據請求路徑在map路由表中查找對應的Handler。而後把請求分配給處理函數

4.提供服務:處理函數根據請求的參數等信息作處理,返回不一樣的信息

5.關閉連接:應用層處理完請求後關閉連接

前置知識

Go基礎語法、web基礎、*壓縮字典樹

源碼分析範圍/大綱

主要分析net/http庫中的server.go文件,可是篇幅有限重點分析(使用mux.XX()簡化代替ServeMux.XX()):

1.ServeMux結構體及其方法:mux.NewServeMux()mux.Handle()mux.HandleFunc()mux.Handler()/mux.handler()mux.ServeHTTP()

2.HandlerFunc結構體及其實現方法:HandlerFunc.ServeHTTP()

3.Handler接口類型

4.函數Handle()和函數HandleFunc()

路由部分就這麼點東西

ServeMux

ServeMux是一個結構體

ServeMux定義

ServeMux是一個HTTP請求多路複用器。它根據註冊模式列表(路由表)將每一個傳入請求的URL匹配,併爲與URL最匹配的模式調用處理程序(handler)。

type ServeMux struct {
    // contains filtered or unexported fields
}

結構體內黑盒,包含已過濾和未導出的字段,其實就是不想讓你知道里面的構造,事實上的構造以下:

type ServeMux struct {
    mu    sync.RWMutex          // 讀寫互斥鎖
    m     map[string]muxEntry   // 路由表
    es    []muxEntry            // 有序數組,從最長到最短排序
    hosts bool                  // whether any patterns contain hostnames
}

ServeMux結構體本質上是由mu讀寫互斥鎖、m路由表、es數組(不少老教程都沒有這個更新字段)和hosts布爾值組成

其中:

1.mu是讀寫互斥鎖,詳情見設計思想

2.m是路由表,路由表本質上就是一個map[string]muxEntry變量,鍵是路徑字符串(由method和傳入參數拼接字符串組成),值是對應的處理結構體muxEntry

3.es是一個有序數組,由長到短維護全部後綴爲/的路由地址,至於爲何要這樣設計,見設計思想

4.布爾類型的hosts屬性標記路由中是否帶有主機名,若hosts值爲true,則路由的起始不能爲/

m路由表中的muxEntry的結構體以下:

type muxEntry struct {
    h        Handler // 處理程序
    pattern  string  // 路由路徑
}

muxEntry本質上是由Handler類和路由路徑字符串組成,其中:

1.h是Handler類型,Handler類型要求實現接口中的ServeHTTP方法

2.pattern實際上和路由表中的key相同

默認多路複用器

net/http包中規定了默認的多路複用器,若是不本身手寫指定則使用默認mux:

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

var defaultServeMux ServeMux

這裏爲何能夠在聲明前使用變量?Dave Cheney告訴我包級別的變量與聲明順序無關,還告訴我這種問題之後去slack上本身問,編譯器作初始化工做的時候會首先初始化包級別的變量,所以不管聲明在哪裏均可以使用。

ServeMux方法

公有方法

mux.NewServeMux()

// NewServeMux allocates and returns a new ServeMux.
func NewServeMux() *ServeMux { return new(ServeMux) }

新建並返回一個ServeMux結構體,爲結構體內字段分配空間。值得注意的是,初始化並返回的結構體字段hosts默認值爲false

mux.Handler()

對於給定的請求,mux.Handler()老是返回非空Handler來使用。若是方法是CONNECT則見私有方法mux.redirectToPathSlash()

mux.Handler()調用私有方法mux.handler(),在mux.handler()內部調用了mux.match()方法來返回匹配pattern的handler

func (mux *ServeMux) Handler(r *Request) (h Handler, pattern string) {

    // CONNECT requests are not canonicalized.
    if r.Method == "CONNECT" {
        // If r.URL.Path is /tree and its handler is not registered,
        // the /tree -> /tree/ redirect applies to CONNECT requests
        // but the path canonicalization does not.
        if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
        }

        return mux.handler(r.Host, r.URL.Path)
    }

    // All other requests have any port stripped and path cleaned
    // before passing to mux.handler.
    host := stripHostPort(r.Host)
    path := cleanPath(r.URL.Path)

    // If the given path is /tree and its handler is not registered,
    // redirect for /tree/.
    if u, ok := mux.redirectToPathSlash(host, path, r.URL); ok {
        return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
    }

    if path != r.URL.Path {
        _, pattern = mux.handler(host, path)
        url := *r.URL
        url.Path = path
        return RedirectHandler(url.String(), StatusMovedPermanently), pattern
    }

    return mux.handler(host, r.URL.Path)
}

對於r.hostr.URL.Path進行了簡單處理,簡要說明一下兩個函數cleanPath()stripHostPort()分別作了什麼工做:

cleanPath()

1.處理無效路由

2.對於斜槓的處理,代替無效的多個斜槓

3.移除全部的.替換爲等效path

簡單來講就是對路徑進行處理爲等效最短路徑,使之能夠在後續查找路由表的過程當中能夠查找到相應鍵值對。

// cleanPath returns the canonical path for p, eliminating . and .. elements.
func cleanPath(p string) string {
    if p == "" {
        return "/"
    }
    if p[0] != '/' {
        p = "/" + p
    }
    np := path.Clean(p)
    // path.Clean removes trailing slash except for root;
    // put the trailing slash back if necessary.
    if p[len(p)-1] == '/' && np != "/" {
        // Fast path for common case of p being the string we want:
        if len(p) == len(np)+1 && strings.HasPrefix(p, np) {
            np = p
        } else {
            np += "/"
        }
    }
    return np
}

stripHostPort()

就是對host格式的規範

// stripHostPort returns h without any trailing ":<port>".
func stripHostPort(h string) string {
    // If no port on host, return unchanged
    if strings.IndexByte(h, ':') == -1 {
        return h
    }
    host, _, err := net.SplitHostPort(h)
    if err != nil {
        return h // on error, return unchanged
    }
    return host
}

mux.ServeHTTP()

mux.ServeHTTP()給最能匹配請求URL的handler發出請求,最後調用實現ServeHTTP()方法的Handler類型的Handler.ServeHTTP()來處理請求

有點繞,總之HTTP的請求首先由mux.ServeHTTP()進行處理,在該函數內部調用了mux.Handler()(見私有方法mux.handler())來選擇處理程序handler(在這個過程當中調用了mux.handler()/mux.RedirectHandler()來查找路由表,最後在mux.handler()的內部調用了mux.match()來最後對路由進行匹配查找返回Handler類型的h)

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

mux.Handle()

在註冊路由/添加路由階段,註冊函數mux.Handle()負責將處理程序和路徑註冊到路由表中,本質上是一個寫表的過程

// 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()

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

    if mux.m == nil {
        mux.m = make(map[string]muxEntry)
    }
    e := muxEntry{h: handler, pattern: pattern}
    mux.m[pattern] = e
    if pattern[len(pattern)-1] == '/' {
        mux.es = appendSorted(mux.es, e)
    }

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

對於路由在表中重複會引起panic,對於後綴爲slash/的路徑,按照長度大小寫入mux.es中,以前分析結構體mux時也提到過這一點。

簡單看一下其實現的排序函數:

func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
    n := len(es)
    i := sort.Search(n, func(i int) bool {
        return len(es[i].pattern) < len(e.pattern)
    })
    if i == n {
        return append(es, e)
    }
    // we now know that i points at where we want to insert
    es = append(es, muxEntry{}) // try to grow the slice in place, any entry works.
    copy(es[i+1:], es[i:])      // Move shorter entries down
    es[i] = e
    return es
}

mux.HandleFunc()

mux.HandleFunc()是我認爲最重要的一個方法,一樣是將handler註冊到路由表中,咱們應該對比mux.HandleFunc()mux.Handle()的區別,其實從函數體來看mux.HandleFunc()算是對mux.Handle()函數的一個再封裝,調用了HandlerFunc())這個適配器函數,本質上是將一個普通函數做爲HTTP請求handler的語法糖,咱們再也不須要實現ServeHTTP()方法,取而代之的是傳入的普通函數只要爲func(ResponseWriter, *Request)類型的,就能夠進行函數的路由,基本上一行代碼就能夠搞定,這也是爲何在官網示例中咱們能夠垂手可得的構建簡單的web程序的緣由。在官網示例的HandleFunc()函數中調用了默認複用器的DefaultServeMux.HandleFunc()方法,開發者只須要本身定義普通函數便可:

// HandleFunc registers the handler function for the given pattern.
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

私有方法

mux.match()

當調用mux.Handler()返回Handler類時在mux.handler()內部會調用mux.match()函數,本質上能夠看做是路由查找的過程(Handle()是路由註冊的過程)

// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    // Check for exact match first.
    v, ok := mux.m[path]
    if ok {
        return v.h, v.pattern
    }

    // Check for longest valid match.  mux.es contains all patterns
    // that end in / sorted from longest to shortest.
    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}

在進行匹配的過程當中:

1.首先在路由表中進行精確匹配,匹配到muxEntry後返回

2.若是在路由表中沒有查詢到,則在有序數組es中進行匹配,從strings.HasPrefix()能夠看出,本質上這是一種模糊匹配,只匹配了相應的前綴,就認定匹配成功

3.若是相應前綴沒法查詢,則認爲匹配失敗,返回nil handler

總結匹配規則一句話描述是: Longer patterns take precedence over shorter ones,長字符串模式優先級大於短字符串模式,優先匹配長字符串

mux.shouldRedirectRLocked()

mux.shouldRedirectRLocked()方法的做用較爲簡單,判斷是否須要對像"/tree/"這種路由的重定向(在ServeMux中對於"/tree"會自動重定向到"/tree/",除非路由表中已有"/tree",此過程在mux.Handler()中調用mux.redirectToPathSlash()完成)

1.判斷路由表中是否存在host+path或者path的組合,若是存在則不須要重定向

2.若是path爲空字符串,則不須要重定向

3.若是當前路由表中存在path+「/」,則須要重定向(例如在註冊時將"/tree/"註冊到表中,則對於"/tree"的路由重定向到了"/tree/",)

func (mux *ServeMux) shouldRedirectRLocked(host, path string) bool {
    p := []string{path, host + path}

    for _, c := range p {
        if _, exist := mux.m[c]; exist {
            return false
        }
    }

    n := len(path)
    if n == 0 {
        return false
    }
    for _, c := range p {
        if _, exist := mux.m[c+"/"]; exist {
            return path[n-1] != '/'
        }
    }

    return false
}

mux.redirectToPathSlash()

mux.redirectToPathSlash()函數肯定是否須要在其路徑後附加"/",一旦判斷須要添加"/"則返回新的url和被重定向後的handler:

func (mux *ServeMux) redirectToPathSlash(host, path string, u *url.URL) (*url.URL, bool) {
    mux.mu.RLock()
    shouldRedirect := mux.shouldRedirectRLocked(host, path)
    mux.mu.RUnlock()
    if !shouldRedirect {
        return u, false
    }
    path = path + "/"
    u = &url.URL{Path: path, RawQuery: u.RawQuery}
    return u, true
}

判斷應該重定向後,返回結尾帶有/的路徑

mux.Handler()中,若是Http.Method爲CONNECT,則會返回RedirectHandler(也是Handler類型的一種)寫入StatusMovedPermanently(見status.go中的定義),調用RedirectHandler.ServeHTTP()來對HTTP請求進行處理

if u, ok := mux.redirectToPathSlash(r.URL.Host, r.URL.Path, r.URL); ok {
            return RedirectHandler(u.String(), StatusMovedPermanently), u.Path
        }

        return mux.handler(r.Host, r.URL.Path)

mux.handler()

mux.handler()函數是mux.Handler()的一種實現

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

進行兩種匹配後都沒有找到相應的handler後,返回NotFoundHandler()

總結

Go 其實支持外部實現的路由器 ListenAndServe 的第二個參數就是用以配置外部路由器的,它是一個 Handler 接口,即外部路由器只要實現了 Handler 接口就能夠,咱們能夠在本身實現的路由器的 ServeHTTP 裏面實現自定義路由功能

HandleFunc()

HandleFunc()是函數

HandleFunc定義

對於給定的模式字符串,HandleFunc將handler函數註冊到相應的路由上。換句話說,當對不一樣的url路徑請求時,給出不一樣的處理邏輯,而HandleFunc能夠實現這種將處理邏輯和url綁定的關係。

函數定義:

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

第一個參數是字符串,第二個參數是handler,HandleFunc處理匹配到的url路徑請求。

HandleFunc()本質上調用了默認複用器的mux.HandleFunc()

例子:

package main

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

func main() {
    h1 := func(w http.ResponseWriter, _ *http.Request) {
        io.WriteString(w, "Hello from a HandleFunc #1!\n")
    }
    h2 := func(w http.ResponseWriter, _ *http.Request) {
        io.WriteString(w, "Hello from a HandleFunc #2!\n")
    }

    http.HandleFunc("/", h1)
    http.HandleFunc("/endpoint", h2)

    log.Fatal(http.ListenAndServe(":8080", nil))
}

HandleFunc優點

HandleFunc函數的存在使得咱們能夠直接將一個func(ResponseWriter, *Request)類型的函數做爲handler,而再也不須要實現Handler這個接口和自定義一個實現ServeHTTP函數的類型了,HandleFunc能夠很是簡便的爲url註冊路徑。

Handle()

Handle()是函數

Handle()定義

和HandleFunc相似,本質上也是調用了默認mux的mux.Handle()方法

函數定義:

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

第一個參數是待匹配的字符串,第二個參數是Handler類型的handler,和上面例子的handler不一樣,須要本身實現一個新的類,而且實現類中的方法ServeHTTP

例子:

package main

import (
    "fmt"
    "log"
    "net/http"
    "sync"
)

type countHandler struct {
    mu sync.Mutex // guards n
    n  int
}

func (h *countHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    h.mu.Lock()
    defer h.mu.Unlock()
    h.n++
    fmt.Fprintf(w, "count is %d\n", h.n)
}

func main() {
    http.Handle("/count", new(countHandler))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Handler

Handler是接口

Handler定義

在go package的官網文檔中是這樣定義Handler的「Handler響應HTTP請求」,理解成中文就是「處理程序」。

Handler是一個接口,定義在net/http原生包中:

type Handler interface {
    ServeHTTP(ResponseWriter, *Request)
}

對於任何實現了ServeHTTP方法的都屬於Handler。

Handler性質

1.Handler只能用來讀取request的body,而不能修改請求

2.先讀取body,而後再寫入resp

3.事實上在真正的開發過程當中,咱們不太會常用Handler,由於net/http給咱們提供了更方便的HandleFunc()函數,而這個函數可讓咱們直接將一個函數做爲handler,在這裏handler是函數類型而非此Handle接口,這種實現較實現ServeHTTP()來講更加方便。

Handler用於處理請求並給予響應,更嚴格地說,用來讀取請求體、並將請求對應的響應字段(respones header)寫入ResponseWriter中,而後返回

HandlerFunc

http還提供了HandlerFunc類,和HandleFunc()函數僅有一字之差

HandlerFunc本質是一個適配器函數,這個類在內部實現了ServeHTTP()函數,所以這個類本質上是一個Handler類型

HandlerFunc()定義

HandlerFunc(f) 是調用f函數的處理程序

函數原型:

type HandlerFunc func(ResponseWriter, *Request)

源碼

// 調用默認ServerMux的HandleFunc方法
func HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {                                          
        DefaultServeMux.HandleFunc(pattern, handler)
}
// 把方法handler轉換成HandlerFunc類型,即實現了Handler接口;再執行Handle方法
func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
        mux.Handle(pattern, HandlerFunc(handler))
}

// 路由器註冊一個handler給指定的parttern
func (mux *ServeMux) Handle(pattern string, handler Handler) {
        ....
}

執行HandleFunc()其實就是爲某一規則的請求註冊處理器。

Handler/HandlerFunc區別

Handler是一個接口

HandlerFunc是Handler類型,是一個適配器函數

Handle()/HandleFunc()區別

HandleFunc()Handle()都是調用DefaultServeMux對應的方法,而DefaultServeMux是ServeMux的實例,ServeMux實現了Handler接口。事實上,HandleFunc()最終仍是會調用Handle方法,以前也提到過了:

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

路由註冊過程

路由註冊過程是由mux結構體內的兩個方法mux.Handle()mux.HandleFunc()實現的

mux.Handle()

以前大體瞭解了函數體,路由註冊過程主要由ServeMux 結構體內的Handle()方法實現的,若是該路由已經存在在路由表內,則會引起Panic

ServeMux結構體內的Handle() 方法大體上幹了這樣幾件事:

1.檢查路由合法性,若不合法則給出報錯信息,報錯信息multiple registrations for /xxx 就屬於路由重複註冊而引起的問題

2.若多路複用器中沒有路由表,則建立路由表

3.若路由合法則建立路由條目muxEntry添加至路由表中

4.若路由最後一個字符帶有"/",則按照自定義排序函數appendSorted()的規則定位索引、插入並排序(儘管我對此函數的執行效率充滿疑惑),返回排序後的數組。

5.若路由的首字母不爲"/",則包含主機名

mux.HandleFunc()

func (mux *ServeMux) HandleFunc(pattern string, handler func(ResponseWriter, *Request)) {
    if handler == nil {
        panic("http: nil handler")
    }
    mux.Handle(pattern, HandlerFunc(handler))
}

Helpful behavior

在以前版本的server.go中,註冊函數mux.Handle是存在一些輔助行爲的,當你將路由路徑設置爲/tree/時,Helpful behavior會隱式永久的幫你將/tree註冊到註冊表中,固然也能夠顯式指定路由進行覆蓋,在對/tree/進行訪問時,/tree的handler會自動將請求重定向到/tree/:

狀態代碼: 301 / Moved Permanently

在如今server.go中,ServeMux結構體內維護了一個es類型的數組,就是從長到短記錄最後一個字母是'/'路由字符串的

在使用mux.match()對路由path進行匹配的時候(詳情見「路由查找過程」),首先查找路由表,當路由表中不存在該路由時,遍歷es數組,匹配其最大長度:

// Find a handler on a handler map given a path string.
// Most-specific (longest) pattern wins.
func (mux *ServeMux) match(path string) (h Handler, pattern string) {
    // Check for exact match first.
    v, ok := mux.m[path]
    if ok {
        return v.h, v.pattern
    }

    // Check for longest valid match.  mux.es contains all patterns
    // that end in / sorted from longest to shortest.
    for _, e := range mux.es {
        if strings.HasPrefix(path, e.pattern) {
            return e.h, e.pattern
        }
    }
    return nil, ""
}

服務請求過程

參考官網的文檔,咱們可使用golang原生的庫net/http來實現一個簡單的web路由示例:

// serve.go
package main

import (
    "fmt"
    "net/http"
)

func main() {
    http.HandleFunc("/", HelloWorld)
    http.ListenAndServe(":8080", nil)
}

func HelloWorld(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello World")
}

其實主函數中只有兩個方法,HandleFunc()ListenAndServe(),整個請求響應的執行流程以下:

  1. 註冊路由過程:首先調用HandleFunc():調用了默認複用器DefaultServeMuxHandleFunc(),調用了DefaultServeMuxHandle,往DefaultServeMux的map[string]muxEntry中增長對應的handler和路由規則。
  2. 實例化server並調用server.ListenAndServe(),調用net.Listen("tcp", addr)監聽端口。啓動一個for循環,在循環體中Accept請求
  3. 對每一個請求實例化一個Conn,而且開啓一個goroutine爲這個請求進行服務go c.serve()
  4. 讀取每一個請求的內容w, err := c.readRequest()
  5. 進入serveHandler.ServeHTTP如有本身實現的mux則使用本身的mux。判斷handler是否爲空,若是沒有設置handler(此例子中爲nil handler),handler就設置爲DefaultServeMux
  6. 調用handler的ServeHttp,進入到DefaultServeMux.ServeHttp
  7. 根據request選擇匹配handler,而且進入到這個handler的ServeHTTP

對於每個HTTP請求,服務端都會起一個協程來進行服務,這部分不在本文討論範圍。對於官網示例:

http.ListenAndServe(":8080", nil)

傳入handler是nil,則使用默認複用器DefaultServeMux,調用HandleFunc時,就是向默認複用器註冊了handler

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

鑑於本文篇幅過小,有機會再分析一下server.go中server.Serve()函數

路由查找過程

如何保證確保訪問''/path/subpath'的時候是先匹配'/path/subpath'而不是匹配'/path/'',是由於在路由查找過程當中的查找規則(以前一樣提到過):

mux.ServerHTTP() -> mux.Handler() -> mux.handler() -> mux.match()

獲得了處理請求的handler,再調用h.ServeHTTP(w, r),去執行相應的handler方法:

type HandlerFunc func(ResponseWriter, *Request)

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

f(w,r)就實現了handler的執行

設計思想

mu讀寫鎖:請求設計併發處理,到底哪些地方用到了併發?

沒時間寫了

hasPrefix() 能夠作到精準匹配麼?

能夠,但不必。由於須要對於模糊路由進行匹配,若是實現上只有路由表m,則對於訪問URL的人來講極不友好,沒法作到有效的輔助提示

爲何須要單獨維護一個後綴爲/的數組es?

1.同上,爲了模糊匹配

2.最重要的是,方便插入排序,儘管時間複雜度沒有那麼樂觀

總結

本文僅僅只是對http原生庫的一小部分進行了解讀,對於生產來講其實並無特別大的幫助,可是掌握原生庫的一些設計思想和設計模式對於理解其餘框架是必定有很大幫助的,畢竟原生庫的做者都是真正的大神。

不少捨本逐末的程序員只注重框架的學習而不注重這種基礎,那和培訓班出來的又有什麼區別呢?仍是應該真正理解一下原生庫,畢竟後人開發的第三方仍是借鑑了這些設計哲學的。

將來展望

由於原生庫自帶的默認多路請求路由器功能有限,而催生了不少路由框架,例如比較有名的框架httprouter,這篇文章比較詳細的講解了httprouter框架,感興趣的能夠先看看和原生庫有什麼不一樣,將來若是有時間,也會更新對這些框架的學習。

對於目前已經存在的web路由框架,已經有人比較過這些框架的優劣了,看這篇文章能夠提早預習一下:

超全的Go Http路由框架性能比較

很早就想系統的講解一下智能爬蟲和爬蟲框架了,由於爬蟲和http這個庫關係很是大。基本上star較多的框架我也都多多少少使用過,部分源碼也閱讀過,固然由於框架自己的複雜性,手撕源碼會更加困難和耗費時間。以前也陸陸續續的簡單介紹過colly等經常使用爬蟲框架,將來會更新更多的關於爬蟲框架的源碼解讀。爬蟲自己的技術含量並不高,可是理解這個框架爲何好、哪裏好,這個技術含量就高不少了,畢竟仍是要和其餘爬蟲框架對比一下才知道各自的優劣。

可是受限於每一個開發者自身的水平,這些框架或多或少都有這那的問題,它們並無github主頁上宣傳的那麼「萬能」,因此在這種比較底層的地方,掌握和使用的區別就很大了,大多數人只會使用而沒有掌握。

固然若是熟悉http庫你就會發現,使用原生函數來寫爬蟲同樣很是流暢。

我堅持認爲:爬蟲使用框架是最後的妥協。

Go的有些設計哲學頗有趣,不是簡簡單單幾萬字的篇幅就能夠講明白的

我不想把本身標榜成Go佈道者或者捲入語言聖戰,Gopher應當是有趣的地鼠:

Gopher is moe, but confusing

參考連接

Go Web:Handler

Package http

golang中ServeMux解析

http.ServeMux解析

Golang web路由實現方式整理總結

GOLANG 中HTTP包默認路由匹配規則閱讀筆記

*

handle、handlefunc和handler、handlerfunc的關係

Golang ServeMux 是如何實現多路處理的

[Golang 路由匹配淺析[1]](https://segmentfault.com/a/11...

相關文章
相關標籤/搜索