GOLANG 中HTTP包預設路由匹配規則閱讀筆記

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

一、執行流程

構建一個簡單http server:

package mainimport (    "log"    "net/http")func main() {    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {        w.Write([]byte("hello world"))    })    log.Fatal(http.ListenAndServe(":8080", nil))}

使用http://127.0.0.1:8080/ 就可以看到輸出了

通過跟蹤http.go包代碼,可以發現執行流程基本如下:

1.建立一個Listener監聽8080連接埠

2.進入for迴圈並Accept請求,沒有請求則處於阻塞狀態

3.接收到請求,並建立一個conn對象,放入goroutine處理(實現高並發關鍵)

4.解析請求來源資訊獲得請求路徑等重要訊息

5.請求ServerHTTP方法,已經通過上一步獲得了ResponseWriter和Request對象

func (sh serverHandler) ServeHTTP(rw ResponseWriter, req *Request) {    //此handler即為http.ListenAndServe 中的第二個參數    handler := sh.srv.Handler     if handler == nil {        //如果handler為空白則使用內部的DefaultServeMux 進行處理        handler = DefaultServeMux    }    if req.RequestURI == "*" && req.Method == "OPTIONS" {        handler = globalOptionsHandler{}    }    //這裡就開始處理http請求    //如果需要使用自訂的mux,就需要實現ServeHTTP方法,即實現Handler介面。    handler.ServeHTTP(rw, req)}

6.進入DefaultServeMux中的邏輯就是根據請求path在map中匹配尋找handler,並交由handler處理

http請求處理流程更多資訊可以參考[《Go Web 編程
》3.3 Go如何使得Web工作](https://github.com/astaxie/build-web-application-with-golang/blob/master/zh/03.3.md)

二、DefaultServeMux 路由匹配規則

先看幾個路由規則:

package mainimport (    "log"    "net/http")func main() {    //規則1    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {        w.Write([]byte("hello world"))    })        //規則2    http.HandleFunc("/path/", func(w http.ResponseWriter, r *http.Request) {        w.Write([]byte("pattern path: /path/ "))    })    //規則3    http.HandleFunc("/path/subpath", func(w http.ResponseWriter, r *http.Request) {        w.Write([]byte("pattern path: /path/subpath"))    })    log.Fatal(http.ListenAndServe(":8080", nil))}

情景一:

訪問:http://127.0.0.1:8080/

返回:hello world

情景二:

訪問:http://127.0.0.1:8080/path

返回:pattern path: /path/

情景三:

訪問:http://127.0.0.1:8080/path/subpath/

返回:pattern path: /path/

情景四:

訪問:http://127.0.0.1:8080/hahaha/

返回:hello world

先說明一些規則吧,再看代碼是怎麼實現的:

1.如果匹配路徑中後帶有/,則會自動增加一個匹配規則不帶/尾碼的,並跳轉轉到path/,解釋了情景二的情境,為什麼匹配到的/path/

2.我設定了這麼多規則為什麼規則一可以通用匹配未設定的路由資訊,而且又不影響已經存在路由, 內部是怎麼實現的?

2.1 添加路由規則

先看兩個struct,這是存放預設路由規則的:

type ServeMux struct {    mu    sync.RWMutex  //處理並發,增加讀寫鎖    m     map[string]muxEntry  //存放規則map,key即為設定的path    hosts bool // whether any patterns contain hostnames(是否包含host)}type muxEntry struct {    explicit bool //是否完全符合    h        Handler//相應匹配規則的handler    pattern  string//匹配路徑}

通過跟蹤http.HandleFunc定位到如下代碼,正是往上面兩個struct中增加規則:

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")    }    //如果已經匹配到了則panic    if mux.m[pattern].explicit {        panic("http: multiple registrations for " + pattern)    }        //增加一個新的匹配規則    mux.m[pattern] = muxEntry{explicit: true, h: handler, pattern: pattern}        //根據path的第一個字母判斷是否有host    if pattern[0] != '/' {        mux.hosts = true    }    //!!這裡看清楚 就是實現了情景二的情況 ,看判斷條件    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}    }}

上面有個Helpful behavior的注釋行為,就是實現了情景二的情況,他是判斷如果匹配的路徑中最後含有/,並且之前也不存在添加去除反斜線的規則的話,就自動給他增加一個301的跳轉指向/path/

2.2 尋找路由規則

路由規則的尋找就是從ServeMux 中的map去匹配尋找的,的到這個handler並執行,只是會有一些處理機制,比如怎麼樣確保訪問/path/subpath的時候是先匹配/path/subpath而不是匹配/path/呢?

當一個請求過來的時候,跟蹤到了mux.match方法:

過程mux.ServerHTTP->mux.Handler->mux.handler->mux.match

func (mux *ServeMux) match(path string) (h Handler, pattern string) {    var n = 0    for k, v := range mux.m {        if !pathMatch(k, path) {            continue        }        //如果匹配到了一個規則,並沒有馬上返回handler,而且繼續匹配並且判斷path的長度是否是最長的,這是關鍵!!!        if h == nil || len(k) > n {            n = len(k)            h = v.h            pattern = v.pattern        }    }    return}

1.這裡就解釋了為什麼設定的精確的path是最優匹配到的,因為它是根據path的長度判斷。
當然也就解釋了為什麼/可以匹配所有(看pathMatch函數就知道了,/是匹配所有的,只是這是最後才被匹配成功)

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

等一下,handler中哪裡有ServeHTTP這個方法??

因為在調用 http.HandleFunc的時候已經將自訂的handler處理函數,強制轉為HandlerFunc類型的,就擁有了ServeHTTP方法:

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的執行。

原文地址:silenceper.com

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.