从一个例子分析gorilla/mux源码
来源:互联网 发布:网络上很红的长发男模 编辑:程序博客网 时间:2024/05/21 17:51
最近终于闲下来,有时间阅读gorilla/mux
。这个是我们在用Golang搭建Web服务器的时候经常用到的库,因此非常重要。这里是我阅读源码和参考了别人的博客之后得到一些总结。
使用的例子
我们从下面这个简单的代码开始追踪分析。
package mainimport ("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() r.Path("/").HandlerFunc(HomeHandler) r.Path("/articles/{category}/{id:[0-9]+}"). HandlerFunc(YourHandler). Name("article") log.Fatal(http.ListenAndServe(":8000", r))}
从例子中可以看出,我们定义了两个处理函数,然后用NewRouter
实例化了一个Router,接着设置相应路径对应的处理函数,并启动端口进行监听。这个过程非常简单。
Router实现
Router是一个结构体,如下:
type Router struct { // Configurable Handler to be used when no route matches. NotFoundHandler http.Handler // Configurable Handler to be used when the request method does not match the route. MethodNotAllowedHandler http.Handler // Parent route, if this is a subrouter. parent parentRoute // Routes to be matched, in order. routes []*Route // Routes by name for URL building. namedRoutes map[string]*Route // See Router.StrictSlash(). This defines the flag for new routes. strictSlash bool // See Router.SkipClean(). This defines the flag for new routes. skipClean bool // If true, do not clear the request context after handling the request. // This has no effect when go1.7+ is used, since the context is stored // on the request itself. KeepContext bool // see Router.UseEncodedPath(). This defines a flag for all routes. useEncodedPath bool}
我们调用了NewRouter
来实例化一个Router,而在源码中,NewRouter
函数只有简单的一行代码。
// NewRouter returns a new router instance.func NewRouter() *Router { return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}}
这里可以看见,它开辟了一个装Route指针的map,然后默认该Router的KeepContext
为false
,意思是在请求被处理完之后清除该请求的上下文。
接下来我们调用了Router的Path
方法,为Router新建一个Route。Router的Path方法源码如下:
// Path registers a new route with a matcher for the URL path.// See Route.Path().func (r *Router) Path(tpl string) *Route { return r.NewRoute().Path(tpl)}
可以看到,这个函数调用NewRoute
返回一个Route对象,然后再调用Route的Path
方法。
// NewRoute registers an empty route.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实现
上面我们调用了Route的Path
函数,那么让我们来看看Route的Path
函数的源码。
func (r *Route) Path(tpl string) *Route { r.err = r.addRegexpMatcher(tpl, false, false, false) return r}
可以看到,这个函数很简单地调用了addRegexpMatcher
函数,加入一个Match到Route中。
能够支持正则路由是gorilla/mux的一个很重要的特性,因为原生的http.ServerMux并不支持正则路由。
// addRegexpMatcher adds a host or path matcher and builder to a route.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}
addRegexpMatcher
方法是Route对象的比较核心的部分. r.getRegexpGroup()
方法继承父路由中的routeRegexpGroup
或者新建一个空的routeRegexpGroup
。
接下来是调用newRouteRegexp
方法,根据request生成一个routeRegexp
,最后再把生成的routeRegexp
追加到Route的matchers中,所以我们现在可以知道Route中的matchers对应的是一个routeRegexp
。
其中,newRouteRegexp
是一个很重要的方法。源码如下,代码中有中文注释:
func newRouteRegexp(tpl string, matchHost, matchPrefix, matchQuery, strictSlash, useEncodedPath bool) (*routeRegexp, error) { // Check if it is well-formed. idxs, errBraces := braceIndices(tpl) //获取大括号在tpl的下标 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]] // 没有匹配到变量的字符串,如例子中的 articles end = idxs[i+1] //把如 id:[0-9]+ 这样的字符串根据:切分,id赋给name, [0-9]+赋给patt parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2) 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 } // Check for capturing groups which used to work in older versions if reg.NumSubexp() != len(idxs)/2 { panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) + "Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)") } // Done! return &routeRegexp{ template: template, matchHost: matchHost, matchQuery: matchQuery, strictSlash: strictSlash, useEncodedPath: useEncodedPath, regexp: reg, reverse: reverse.String(), varsN: varsN, varsR: varsR, }, nil}
说完Path
的一系列调用,我们来看HandlerFunc
。 HandlerFunc
是Route对象的方法,可以给一条Route注册一个回调函数。
// HandlerFunc sets a handler function for the route.func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route { return r.Handler(http.HandlerFunc(f))}
在r.Handler(http.HandlerFunc(f))
中, 再次调用了 Route的 Handler
函数, 其中http.HandlerFunc
是一个类型。官网文档是这样写的,可以看出,对自定义的回调函数是需要进行这种转换的。
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
来监听端口。而该函数第二个参数应该传如一个Handler类型的变量。我们知道,Handler是一个接口,它要求实现ServeHTTP
方法。我们传了一个Router的实例进去,为什么可以呢?这是因为Router里面实现了ServeHTTP
的方法。实现如下:
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { if !r.skipClean { path := req.URL.Path if r.useEncodedPath { path = req.URL.EscapedPath() } // 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 && match.MatchErr == ErrMethodMismatch { handler = methodNotAllowedHandler() } if handler == nil { handler = http.NotFoundHandler() } if !r.KeepContext { defer contextClear(req) } handler.ServeHTTP(w, req)}
其中skipClean
, useEncodedPath
默认为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 } } if match.MatchErr == ErrMethodMismatch { if r.MethodNotAllowedHandler != nil { match.Handler = r.MethodNotAllowedHandler return true } else { return false } } // Closest match for a router (includes sub-routers) if r.NotFoundHandler != nil { match.Handler = r.NotFoundHandler match.MatchErr = ErrNotFound return true } match.MatchErr = ErrNotFound return false}
从这个例子追踪的源码分析到这里就结束了,但是,gorilla/mux仍然有很多的部分我没有阅读到。并且,gorilla/mux仍然在不断修改完善,所以过一段时间看到的源码和现在可能又不一样了。
这是一个很简单的例子,希望以后有机会再深入研究。
- 从一个例子分析gorilla/mux源码
- Negroni和Gorilla/mux 解析 Golang
- 【原创】k8s源码分析-----Mux And Broadcaster
- 从一个例子开始分析AIDL原理
- Webpack-源码三,从源码分析如何写一个plugin
- Webpack-源码四,从源码分析如何写一个loader
- MUX
- 从源码角度分析nodejs如何处理一个HTTP请求
- 从源码分析IntentService
- 从源码分析 HashMap
- 从源码分析AsyncTask
- IntentService 从源码分析
- 从新闻报道分析的例子
- Android AIDL分析例子源码
- Android AIDL 分析 例子 源码
- Android AIDL分析例子源码
- Android AIDL 分析 例子 源码
- Android AIDL 分析 例子 源码
- 传智播客郑州校区PHP就业9期开班典礼
- IO流
- 【参会须知】2017中国软件技术大会倒计时1天!
- 数据结构实验之图论十:判断给定图是否存在合法拓扑序列
- ACL配置(十分基础)
- 从一个例子分析gorilla/mux源码
- commons-lang常用API简介
- 剑指offer---二进制中1的个数(10)
- Spring整合单机版SolrJ
- MultipartFile简单Junit测试方法
- iptables的基本规则
- kerbernets 中CPU 亲和性实现
- QT中字符串的相关使用
- HttpClient4.1入门教程-利用官方例子讲解httpClient4.1的用法