mux簡介
go
語言中一個強大的web
路由,在這里我準(zhǔn)備理解其原理后實現(xiàn)一個python
版的mux
,同時也為了進(jìn)一步學(xué)習(xí)go。
0x01 Router淺析
首先我們從一個簡單的demo入手,來追蹤mux
的內(nèi)部實現(xiàn)原理。新建一個main.go
到go的工作目錄下。
package main
import (
"github.com/gorilla/mux"
"log"
"net/http"
)
func YourHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Gorilla!\n"))
}
func HomeHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello Golang!\n"))
}
func main() {
r := mux.NewRouter()
// Routes consist of a path and a handler function.
r.Path("/").HandlerFunc(HomeHandler)
r.Path("/articles/{category}/{id:[0-9]+}").
HandlerFunc(YourHandler).
Name("article")
// Bind to a port and pass our router in
log.Fatal(http.ListenAndServe(":8000", r))
}
從demo中可以看出,先簡單的調(diào)用NewRouter方法實例化Router,默認(rèn)KeepContext
為False
,暫時猜測KeepContext
應(yīng)該跟keepalive
選項有關(guān)。
接下來調(diào)用Path方法,為Router
簡單的新建一個Route
。函數(shù)實現(xiàn)是,NewRoute
方法返回的是一個Route對象,然后再調(diào)用Route
對象的Path方法,
func (r *Router) Path(tpl string) *Route {
return r.NewRoute().Path(tpl)
}
0x02 Route的實現(xiàn)
NewRoute的函數(shù)實現(xiàn)如下,在Router
中新建的每一條Route
都是從Router
中繼承一些選項的,如strctSlash
,這樣比較方便支持多Router
。
func (r *Router) NewRoute() *Route {
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
r.routes = append(r.routes, route)
return route
}
Route
的Path
方法只是簡單的調(diào)用addRegexpMatcher方法,給Route增加一個Match,實現(xiàn)如下:
func (r *Route) Path(tpl string) *Route {
r.err = r.addRegexpMatcher(tpl, false, false, false)
return r
}
0x03 routeRegexp原理
addRegexpMatcher
方法是Route對象的比較核心的部分,r.getRegexpGroup()
方法和繼承父路由中的routeRegexpGroup
或者新建一個空的routeRegexpGroup
。接下來是調(diào)用newRouteRegexp方法,根據(jù)request
生成一個routeRegexp
,最后再把生成的routeRegexp
追加到Route
的matchers
中,所以我們現(xiàn)在可以知道Route
中的matchers
對應(yīng)的是一個routeRegexp
。addRegexpMatcher
函數(shù)實現(xiàn)如下:
func (r *Route) addRegexpMatcher(tpl string, matchHost, matchPrefix, matchQuery bool) error {
if r.err != nil {
return r.err
}
r.regexp = r.getRegexpGroup()
if !matchHost && !matchQuery {
if len(tpl) == 0 || tpl[0] != '/' {
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
}
if r.regexp.path != nil {
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
}
}
rr, err := newRouteRegexp(tpl, matchHost, matchPrefix, matchQuery, r.strictSlash, r.useEncodedPath)
if err != nil {
return err
}
for _, q := range r.regexp.queries {
if err = uniqueVars(rr.varsN, q.varsN); err != nil {
return err
}
}
if matchHost {
if r.regexp.path != nil {
if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
return err
}
}
r.regexp.host = rr
} else {
if r.regexp.host != nil {
if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
return err
}
}
if matchQuery {
r.regexp.queries = append(r.regexp.queries, rr)
} else {
r.regexp.path = rr
}
}
r.addMatcher(rr)
return nil
}
newRouteRegexp對象是Route
對象的重中之重。函數(shù)實現(xiàn)和注釋如下:
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) {
// Check if it is well-formed.
idxs, errBraces := braceIndices(tpl) //獲取大括號在tpl的下標(biāo)
if errBraces != nil {
return nil, errBraces
}
// Backup the original.
template := tpl
// Now let's parse it.
defaultPattern := "[^/]+"
if matchQuery {
defaultPattern = "[^?&]*"
} else if matchHost {
defaultPattern = "[^.]+"
matchPrefix = false
}
// Only match strict slash if not matching
if matchPrefix || matchHost || matchQuery {
strictSlash = false
}
// Set a flag for strictSlash.
endSlash := false
if strictSlash && strings.HasSuffix(tpl, "/") {
tpl = tpl[:len(tpl)-1]
endSlash = true
}
varsN := make([]string, len(idxs)/2)
varsR := make([]*regexp.Regexp, len(idxs)/2)
pattern := bytes.NewBufferString("")
pattern.WriteByte('^')
reverse := bytes.NewBufferString("")
var end int
var err error
for i := 0; i < len(idxs); i += 2 {
// Set all values we are interested in.
raw := tpl[end:idxs[i]] // 沒有匹配到變量的字符串,如demo中的 articles
end = idxs[i+1]
parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) //把如 id:[0-9]+ 這樣的字符串根據(jù):切分,id賦給name, [0-9]+賦給patt
name := parts[0]
patt := defaultPattern
if len(parts) == 2 {
patt = parts[1]
}
// Name or pattern can't be empty.
if name == "" || patt == "" {
return nil, fmt.Errorf("mux: missing name or pattern in %q",
tpl[idxs[i]:end])
}
// Build the regexp pattern.
//格式化成如下形式 articles/(?P<v0>[^/]+)/(?P<v0>[0-9]+)
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
// Build the reverse template.
fmt.Fprintf(reverse, "%s%%s", raw)
// Append variable name and compiled pattern.
varsN[i/2] = name
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
if err != nil {
return nil, err
}
}
// Add the remaining.
raw := tpl[end:]
pattern.WriteString(regexp.QuoteMeta(raw))
if strictSlash {
pattern.WriteString("[/]?")
}
if matchQuery {
// Add the default pattern if the query value is empty
if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
pattern.WriteString(defaultPattern)
}
}
if !matchPrefix {
pattern.WriteByte('$')
}
reverse.WriteString(raw)
if endSlash {
reverse.WriteByte('/')
}
// Compile full regexp.
reg, errCompile := regexp.Compile(pattern.String())
if errCompile != nil {
return nil, errCompile
}
// Done!
return &routeRegexp{
template: template,
matchHost: matchHost,
matchQuery: matchQuery,
strictSlash: strictSlash,
useEncodedPath: useEncodedPath,
regexp: reg,
reverse: reverse.String(),
varsN: varsN,
varsR: varsR,
}, nil
}
Route中HandlerFunc的作用
HandlerFunc
是Route
對象的方法,可以給一條Route注冊一個回調(diào)函數(shù)。HandlerFunc函數(shù)實現(xiàn)是
func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
return r.Handler(http.HandlerFunc(f))
}
在r.Handler(http.HandlerFunc(f))
中, 再次調(diào)用了 Route
的 Handler
函數(shù), 其中http.HandlerFunc
是一個類型。官網(wǎng)文檔是這樣寫的,可以看出,對自定義的回調(diào)函數(shù)是需要進(jìn)行這種轉(zhuǎn)換的。
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.
ListenAndServe啟動服務(wù)
注冊完單條路由后,就開始處理http
請求啦。
http.ListenAndServe(":8000", r)
其函數(shù)簽名是,
func ListenAndServe(addr string, handler Handler) error
而Handler是一個接口,它定義了ServeHTTP方法,而我們在ListenAndServe中把Router的一個實例傳進(jìn)去了。所以在這里會調(diào)用Router的ServeHTTP方法
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
ServeHTTP實現(xiàn)如下
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
if !r.skipClean {
path := req.URL.Path
if r.useEncodedPath {
path = getPath(req)
}
// Clean path to canonical form and redirect.
if p := cleanPath(path); p != path {
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
// http://code.google.com/p/go/issues/detail?id=5252
url := *req.URL
url.Path = p
p = url.String()
w.Header().Set("Location", p)
w.WriteHeader(http.StatusMovedPermanently)
return
}
}
var match RouteMatch
var handler http.Handler
if r.Match(req, &match) {
handler = match.Handler
req = setVars(req, match.Vars)
req = setCurrentRoute(req, match.Route)
}
if handler == nil {
handler = http.NotFoundHandler()
}
if !r.KeepContext {
defer contextClear(req)
}
handler.ServeHTTP(w, req)
}
其中skipClean
, useEncodedPath
默認(rèn)為false
。核心部分是從 r.Match(req, &match)
開始的,Match方法定義如下,首先會遍歷Router
中的所有路由route的Match方法,如有匹配到,則直接返回,否則返回NotFoundHandler
。
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
for _, route := range r.routes {
if route.Match(req, match) {
return true
}
}
// Closest match for a router (includes sub-routers)
if r.NotFoundHandler != nil {
match.Handler = r.NotFoundHandler
return true
}
return false
}
注意到Match的函數(shù)簽名中的req和match都是指針,而這個match是從ServeHTTP方法傳過來的。
Match(req *http.Request, match *RouteMatch) bool
Route的Match方法定義如下
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
if r.buildOnly || r.err != nil {
return false
}
// Match everything.
for _, m := range r.matchers {
if matched := m.Match(req, match); !matched {
return false
}
}
// Yay, we have a match. Let's collect some info about it.
if match.Route == nil {
match.Route = r
}
if match.Handler == nil {
match.Handler = r.handler
}
if match.Vars == nil {
match.Vars = make(map[string]string)
}
// Set variables.
if r.regexp != nil {
r.regexp.setMatch(req, match, r)
}
return true
}
核心部分是再次遍歷Route
的matchers
,而matchers
從哪里來呢?