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

1、執行流程

構建一個簡單http server:git

package main

import (
    "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/ 就能夠看到輸出了github

經過跟蹤http.go包代碼,能夠發現執行流程基本以下:golang

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處理web

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

2、DefaultServeMux 路由匹配規則

先看幾個路由規則:併發

package main

import (
    "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))
}

情景一:app

訪問:http://127.0.0.1:8080/ 函數

返回:hello world高併發

情景二:ui

訪問: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

相關文章
相關標籤/搜索