深入解析go web框架marcron 一 路由

介绍

祝大家中秋假日快乐!(~ ̄▽ ̄)~, 连续肝了三篇文章剖析了我经常使用的macaron框架,希望大家在选择web框架有一种更好的选择。顺便说一下,好多面试官都不了解这个框架(づ ̄3 ̄)づ╭❤~

github地址:GitHub - go-macaron/macaron: Package macaron is a high productive and modular web framework in Go.

官方文档地址:Welcome - Macaron Documentation

下面是该框架在官方宣称的特性,个人感觉inject 实现的依赖注入是很大的亮点,使用案例将会在第三篇Grafana是怎么运用该框架文章详细讲解。关于该框架的性能,后面我会专门写篇文章进行压测,对比其他框架。

主要特性

  • 支持子路由的强大路由设计

  • 支持灵活多变的路由组合

  • 支持无限路由组的无限嵌套

  • 支持直接集成现有的服务

  • 支持运行时动态设置需要渲染的模板集

  • 支持使用内存文件作为静态资源和模板文件

  • 支持对模块的轻松接入与解除

  • 采用 inject 提供的便利的依赖注入

  • 采用更好的路由层和更少的反射来提升执行速度

使用案例

  • Gogs: A painless self-hosted Git Service

  • Grafana: The open source analytics & monitoring solution for every database

  • Peach Docs: A modern documentation web server

  • Go Walker: Go online API documentation

  • Intel Stack: A 100% free intelligence marketplace

Hello world

package main
​
import ("log""net/http"
​"gopkg.in/macaron.v1"
)
​
func main() {m := macaron.Classic()m.Get("/", myHandler)
​log.Println("Server is running...")log.Println(http.ListenAndServe("0.0.0.0:4000", m))
}
​
func myHandler(ctx *macaron.Context) string {return "the request path is: " + ctx.Req.RequestURI
}
 
  • m 代表实例,类比于其他框架的什么Engine ,app

  • m.Get代表注册路由,实例身上都会有这些方法

  • macaron 的handler统一为接口形式,而且不式固定的func(ctx *context)形式,相比较于其他web框架,中间件实现的方式就可以很多种

路由解析

路由注册方法

pkg/mod/gopkg.in/macaron.v1@v1.4.0/router.go

下面是所有方法的注册,其实最后都只调用了个Handle方法而已

func (r *Router) Group(pattern string, fn func(), h ...Handler) {r.groups = append(r.groups, group{pattern, h})fn()r.groups = r.groups[:len(r.groups)-1]
}
​
// Get is a shortcut for r.Handle("GET", pattern, handlers)
func (r *Router) Get(pattern string, h ...Handler) (leaf *Route) {leaf = r.Handle("GET", pattern, h)if r.autoHead {r.Head(pattern, h...)}return leaf
}
​
// Patch is a shortcut for r.Handle("PATCH", pattern, handlers)
func (r *Router) Patch(pattern string, h ...Handler) *Route {return r.Handle("PATCH", pattern, h)
}
​
// Post is a shortcut for r.Handle("POST", pattern, handlers)
func (r *Router) Post(pattern string, h ...Handler) *Route {return r.Handle("POST", pattern, h)
}
​
// Put is a shortcut for r.Handle("PUT", pattern, handlers)
func (r *Router) Put(pattern string, h ...Handler) *Route {return r.Handle("PUT", pattern, h)
}
​
// Delete is a shortcut for r.Handle("DELETE", pattern, handlers)
func (r *Router) Delete(pattern string, h ...Handler) *Route {return r.Handle("DELETE", pattern, h)
}
​
// Options is a shortcut for r.Handle("OPTIONS", pattern, handlers)
func (r *Router) Options(pattern string, h ...Handler) *Route {return r.Handle("OPTIONS", pattern, h)
}
​
// Head is a shortcut for r.Handle("HEAD", pattern, handlers)
func (r *Router) Head(pattern string, h ...Handler) *Route {return r.Handle("HEAD", pattern, h)
}
​
// Any is a shortcut for r.Handle("*", pattern, handlers)
func (r *Router) Any(pattern string, h ...Handler) *Route {return r.Handle("*", pattern, h)
}
​
// Route is a shortcut for same handlers but different HTTP methods.
//
// Example:
//        m.Route("/", "GET,POST", h)
func (r *Router) Route(pattern, methods string, h ...Handler) (route *Route) {for _, m := range strings.Split(methods, ",") {route = r.Handle(strings.TrimSpace(m), pattern, h)}return route
}

路由注册

Macaron实例继承Router,所以也有Router相关方法

// inject.Injector methods can be invoked to map services on a global level.
type Macaron struct {*Router //继承router
}

来张图感受下是怎么路由注册的

 

注册路由举例

m.Get("/", myHandler)

最后都会调用route的handle方法

// Handle registers a new request handle with the given pattern, method and handlers.
func (r *Router) Handle(method string, pattern string, handlers []Handler) *Route {if len(r.groups) > 0 {groupPattern := ""h := make([]Handler, 0)for _, g := range r.groups {groupPattern += g.patternh = append(h, g.handlers...)}
​pattern = groupPattern + patternh = append(h, handlers...)handlers = h}handlers = validateAndWrapHandlers(handlers, r.handlerWrapper)
​return r.handle(method, pattern, func(resp http.ResponseWriter, req *http.Request, params Params) {c := r.m.createContext(resp, req)c.params = paramsc.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))c.handlers = append(c.handlers, r.m.handlers...)//添加全局默认handlec.handlers = append(c.handlers, handlers...) //添加用户路由的handlc.run()})//回调函数,当匹配到路径后会执行这个回调
}

r.handle

就是检查相关参数是否正确,然后添加路由

// handle adds new route to the router tree.
func (r *Router) handle(method, pattern string, handle Handle) *Route {method = strings.ToUpper(method) //将方法变成大写
​var leaf *Leaf// Prevent duplicate routes. //如果该方法和路径有存在的节点,就直接从map里面去找,if leaf = r.getLeaf(method, pattern); leaf != nil {return &Route{r, leaf}}
​// Validate HTTP methods.if !_HTTP_METHODS[method] && method != "*" {panic("unknown HTTP method: " + method)}
​// Generate methods need register.methods := make(map[string]bool)if method == "*" {for m := range _HTTP_METHODS {methods[m] = true}} else {methods[method] = true}
​// Add to router tree.for m := range methods {if t, ok := r.routers[m]; ok { //如果该方法存在,说明已经有树的根节点了leaf = t.Add(pattern, handle)} else {t := NewTree() //如果没有存在,创建根节点,将方法和handle 添加进去leaf = t.Add(pattern, handle)r.routers[m] = t//最后将节点添加到全局map里面}r.add(m, pattern, leaf) }return &Route{r, leaf}
}

tree.add

调用addNextSegment添加节点

func (t *Tree) Add(pattern string, handle Handle) *Leaf {pattern = strings.TrimSuffix(pattern, "/")return t.addNextSegment(pattern, handle)
}

tree.addNextSegment

func (t *Tree) addNextSegment(pattern string, handle Handle) *Leaf {pattern = strings.TrimPrefix(pattern, "/")
​i := strings.Index(pattern, "/")//如果i==-1,说明已经实path的最后一段了if i == -1 {return t.addLeaf(pattern, handle) //添加叶子节点}return t.addSubtree(pattern[:i], pattern[i+1:], handle)//添加子树节点
}
  • 将pattern进行分段,将pattern 拆分成的第一段和后面的pattern部分作为传入addSubtree的参数,比如/api/v1/xxoo,在经过上面会变成,pattern[:i]=api ,pattern[i+1:]=v1/xxoo

  • 该方法将解析注册的pattern每一段,直到最后一段,然后添加叶子节点,整个过程是个递归

addLeaf

如果不包含/,说明是最后一段,添加进叶子节点

func (t *Tree) addLeaf(pattern string, handle Handle) *Leaf {for i := 0; i < len(t.leaves); i++ {if t.leaves[i].pattern == pattern {return t.leaves[i] //如果该树的叶子节点已经存在,则返回这个叶子节点}}leaf := NewLeaf(t, pattern, handle) //创建一个叶子节点// Add exact same leaf to grandparent/parent level without optional.if leaf.optional {parent := leaf.parentif parent.parent != nil {parent.parent.addLeaf(parent.pattern, handle) //添加到祖父节点} else {parent.addLeaf("", handle) // Root tree can add as empty pattern.}}i := 0for ; i < len(t.leaves); i++ {if leaf.typ < t.leaves[i].typ { //找到该叶子节点合适的位置,后面匹配将按照类型最小到大匹配break}}if i == len(t.leaves) {t.leaves = append(t.leaves, leaf)} else {t.leaves = append(t.leaves[:i], append([]*Leaf{leaf}, t.leaves[i:]...)...)}return leaf
}
  • 在new一个叶子节点的时候,将Pattern段进行拆分用checkPattern函数,拆分出类型,和参数checkPattern在下面会有说明,其中会给叶子节点optional赋值,代表是可选的,例如: /user/?:id匹配 /user/ 和 /user/123

func NewLeaf(parent *Tree, pattern string, handle Handle) *Leaf {typ, rawPattern, wildcards, reg := checkPattern(pattern)optional := falseif len(pattern) > 0 && pattern[0] == '?' {optional = true}return &Leaf{parent, typ, pattern, rawPattern, wildcards, reg, optional, handle}
}
  • 从父节点的其他叶子节点遍历,找到比这节点优先级大的索引,然后将这个叶子节点,插入到合适的位置

  • 如果发现有可选项参数并且不是根节点,则在祖父节点上面再挂一个带handle的叶子节点,如果是根节点直接挂在根节点上,上一张图说明匹配过程。leaf 是可选的话,那么在祖父节点再挂一个叶子节点,在进行匹配的时候,如果是叶子节点就匹配/hello或者匹配/?:id 这个节点

 

typ 优先级说明,匹配按照顺序将从小到大进行匹配

const (_PATTERN_STATIC    patternType = iota // /home_PATTERN_REGEXP                       // /:id([0-9]+)_PATTERN_PATH_EXT                     // /*.*_PATTERN_HOLDER                       // /:user_PATTERN_MATCH_ALL                    // /*
)

addSubtree

如果包含,继续添加子树

func (t *Tree) addSubtree(segment, pattern string, handle Handle) *Leaf {for i := 0; i < len(t.subtrees); i++ {if t.subtrees[i].pattern == segment { //如果该段已经存在,则继续向下添加,直到最后添加叶子节点return t.subtrees[i].addNextSegment(pattern, handle)}}subtree := NewSubtree(t, segment)//如果不存在添加子树i := 0for ; i < len(t.subtrees); i++ {if subtree.typ < t.subtrees[i].typ {break}}if i == len(t.subtrees) {t.subtrees = append(t.subtrees, subtree)} else {t.subtrees = append(t.subtrees[:i], append([]*Tree{subtree}, t.subtrees[i:]...)...)}return subtree.addNextSegment(pattern, handle)
}

创建子树NewSubtree

func NewSubtree(parent *Tree, pattern string) *Tree {typ, rawPattern, wildcards, reg := checkPattern(pattern)return &Tree{parent, typ, pattern, rawPattern, wildcards, reg, make([]*Tree, 0, 5), make([]*Leaf, 0, 5)}
}

checkPattern

这个方法用来获取pattern的类型,然后给subtree进行赋值,下面这些拆分,我将在匹配的时候,举例,

func checkPattern(pattern string) (typ patternType, rawPattern string, wildcards []string, reg *regexp.Regexp) {pattern = strings.TrimLeft(pattern, "?")//将?去掉rawPattern = getRawPattern(pattern)if pattern == "*" { // 如果pattern包含*号,则是通配符typ = _PATTERN_MATCH_ALL  //赋值通配符类型} else if pattern == "*.*" { //如果是*.* 则是path.ext 这种形式typ = _PATTERN_PATH_EXT} else if strings.Contains(pattern, ":") {//如果包含:是正则类型,也有可能是普通的参数类型typ = _PATTERN_REGEXPpattern, wildcards = getWildcards(pattern)if pattern == "(.+)" {//说明是普通的参数类型typ = _PATTERN_HOLDER} else {reg = regexp.MustCompile(pattern) //创建正则实例}}return typ, rawPattern, wildcards, reg
}
  • getWildcards返回的参数是pattern, wildcards,如果pattern是(.+)说明只有类似:id 这种形式,没有正则,

    如果是pattern不是,说明里面有正则,创建正则实例

getRawPattern 去掉所有的正则但是保留通配符

// getRawPattern removes all regexp but keeps wildcards for building URL path.
func getRawPattern(rawPattern string) string {rawPattern = strings.Replace(rawPattern, ":int", "", -1)rawPattern = strings.Replace(rawPattern, ":string", "", -1)for {startIdx := strings.Index(rawPattern, "(") if startIdx == -1 {break}closeIdx := strings.Index(rawPattern, ")")if closeIdx > -1 {rawPattern = rawPattern[:startIdx] + rawPattern[closeIdx+1:]}}return rawPattern
}
  • 步骤1:去掉路由中的Shortcuts形式

    Shortcuts:

    • /user/:id:int, :int is shortcut for ([0-9]+).

    • /user/:name:string, :string is shortcut for ([\w]+).

  • 去掉括号中间的正则内容

    /user/:username([\w]+) 将返回:username

举个例子

func main()  {fmt.Println(getRawPattern("/:int/:string")) //  结果为 //fmt.Println(getRawPattern("/user/:id([0-9]+)")) //  结果为 /user/:idfmt.Println(getRawPattern("/cms_:id([0-9]+).html")) //结果为 /cms_:id.html
}

getWildcards作用

func getWildcards(pattern string) (string, []string) {wildcards := make([]string, 0, 2)// Keep getting next wildcard until nothing is left.var wildcard string //循环匹配,直到没有关键词为止for {wildcard, pattern = getNextWildcard(pattern)if len(wildcard) > 0 {wildcards = append(wildcards, wildcard)} else {break}}return pattern, wildcards
}
// getNextWildcard tries to find next wildcard and update pattern with corresponding regexp.
func getNextWildcard(pattern string) (wildcard, _ string) {//wildcardPattern是regexp.MustCompile(`:[a-zA-Z0-9]+`)的正则实例,意思是找到这个实例的位置//例如/user/:id([0-9]+),将会匹配到0,3的位置pos := wildcardPattern.FindStringIndex(pattern)if pos == nil {return "", pattern}wildcard = pattern[pos[0]:pos[1]] //取出上面的关键词例如:id// Reach last character or no regexp is given.if len(pattern) == pos[1] {//说明已经结束了只是/user/:id形式,那么将:id替换成(.+)return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)} else if pattern[pos[1]] != '(' {//如果pos[1]这个位置不是括号的话,那么就有可能是/user/:id:int或者/user/:name:string形式//然后替换成相应的正则表达式switch {case isSpecialRegexp(pattern, ":int", pos):pattern = strings.Replace(pattern, ":int", "([0-9]+)", 1)case isSpecialRegexp(pattern, ":string", pos):pattern = strings.Replace(pattern, ":string", "([\\w]+)", 1)default://到这里来了说明已经结束了,将关键词替换成(.+)return wildcard, strings.Replace(pattern, wildcard, `(.+)`, 1)}}// Cut out placeholder directly.return wildcard, pattern[:pos[0]] + pattern[pos[1]:]
}
  • getWildcards循环获取url中的Wildcard关键字,然后将:int或者:string这种替换成相对应的正则表达式

  • 其实会有疑惑,default为什么要将关键字替换(.+),说明找到:id 这种形式时后面有可能是其它字符不是数字不是字母,替换成(.+)能匹配到这个关键词。

    例如 /user/:username@xxx 这个路由在请求为 /user/:yang@xxx 依然能匹配到:username=yang,是不是感觉很强大 (*・ω-q)

举个例子

func main()  {fmt.Println(getNextWildcard("/user/:id([0-9]+)")) //  结果为 :id /user/([0-9]+)fmt.Println(getNextWildcard("/cms_:id([0-9]+).html")) //结果为 :id /cms_([0-9]+).htmlfmt.Println(getNextWildcard("/:int")) //  结果为 :int /(.+)fmt.Println(getNextWildcard("/hello/*")) //  结果为 /hello/*fmt.Println(getNextWildcard("/date/*/*/*/events")) //  结果为  /date/*/*/*/eventsfmt.Println(getNextWildcard("/user/:username([\\\\w]+)")) //  结果为 :username /user/([\\w]+)fmt.Println(getNextWildcard("/user/:id([0-9]+)")) //  结果为:id /user/([0-9]+)fmt.Println(getNextWildcard("/user/*.*")) //  结果为 /user/*.*}

(.+)是什么?

(.+)默认是贪婪匹配

(.+?)为惰性匹配

疑问号让.+的搜索模式从贪婪模式变成惰性模式。

var str = 'aaa123456bbb'

<.+?>会匹配

<.+>会匹配123456

路由匹配

首先来看http 官方库的方法

http.ListenAndServe("0.0.0.0:4000", m)

m 必须实现下面的接口,在请求到来时,http库会将请求,响应传进来

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

go/pkg/mod/gopkg.in/macaron.v1@v1.4.0/router.go+211

// ServeHTTP is the HTTP Entry point for a Macaron instance.
// Useful if you want to control your own HTTP server.
// Be aware that none of middleware will run without registering any router.
func (m *Macaron) ServeHTTP(rw http.ResponseWriter, req *http.Request) {if m.hasURLPrefix {  //去掉前缀,如果配置了URL前缀,想到于你想配置/api/v1/user/xxoo,实际写/user/xxoo//但前端必须写/api/v1/user/xxoo这个路由req.URL.Path = strings.TrimPrefix(req.URL.Path, m.urlPrefix)}for _, h := range m.befores {if h(rw, req) { //前置中间件return}}//调用路由的处理方法,处理http请求m.Router.ServeHTTP(rw, req)
}

将调用m.RouterServeHTTP的方法,来看看这里面做了些什么

func (r *Router) ServeHTTP(rw http.ResponseWriter, req *http.Request) {if t, ok := r.routers[req.Method]; ok { //先从routers根据方法取出树的根节点来// Fast match for static routesleaf := r.getLeaf(req.Method, req.URL.Path) //从map 里面找到路由,这种情况只能匹配静态路由//如果带参数,或者带通配符,就匹配不到,找到了直接调用处理方法。if leaf != nil {leaf.handle(rw, req, nil)return}h, p, ok := t.Match(req.URL.EscapedPath())if ok {if splat, ok := p["*0"]; ok {p["*"] = splat // Easy name.}h(rw, req, p)return}}r.notFound(rw, req) //将调用notFound响应没有找到路由
}
  • 首先从map里面找静态路由,如果没有找到,开始进行Match匹配

  • 匹配到了调用handle 处理

  • 如果都没匹配到,notFound响应没有找到,这个可以handler可以自己配置

EscapedPath有什么用呢

参考官方的解释:EscapedPath

package mainimport ("fmt""log""net/url"
)func main() {u, err := url.Parse("http://example.com/x/y%2Fz")if err != nil {log.Fatal(err)}fmt.Println("Path:", u.Path)//Path:  /x/y/zfmt.Println("RawPath:", u.RawPath)// /x/y%2Fzfmt.Println("EscapedPath:", u.EscapedPath()) // /x/y%2Fz
}

就是获取u.path 的转义形式

从map里面匹配静态路由getleaf

// getLeaf returns Leaf object if a route has been registered.
func (rm *routeMap) getLeaf(method, pattern string) *Leaf {rm.lock.RLock()defer rm.lock.RUnlock()return rm.routes[method][pattern]
}

带有参数的匹配过程t.match

func (t *Tree) Match(url string) (Handle, Params, bool) {url = strings.TrimPrefix(url, "/")url = strings.TrimSuffix(url, "/")//去掉url前后的/params := make(Params)handle, ok := t.matchNextSegment(0, url, params)return handle, params, ok
}

matchNextSegment

func (t *Tree) matchNextSegment(globLevel int, url string, params Params) (Handle, bool) {i := strings.Index(url, "/")if i == -1 {return t.matchLeaf(globLevel, url, params)}return t.matchSubtree(globLevel, url[:i], url[i+1:], params)
}

从上面看已经去掉url 了,如果这时候里面没/符号,说明这个是叶子节点,走叶子节点的匹配,要不然走匹配子树的逻辑

匹配叶子

func (t *Tree) matchLeaf(globLevel int, url string, params Params) (Handle, bool) {url, err := PathUnescape(url) //获取正常不带转义的路径if err != nil {return nil, false}for i := 0; i < len(t.leaves); i++ { //循环遍历该树的叶子节点类型,switch t.leaves[i].typ {case _PATTERN_STATIC: //如果是静态类型则直接匹配,然后返回trueif t.leaves[i].pattern == url {return t.leaves[i].handle, true}case _PATTERN_REGEXP://如果是正则,results := t.leaves[i].reg.FindStringSubmatch(url)// Number of results and wildcasrd should be exact same.if len(results)-1 != len(t.leaves[i].wildcards) {break}for j := 0; j < len(t.leaves[i].wildcards); j++ {params[t.leaves[i].wildcards[j]] = results[j+1]}return t.leaves[i].handle, truecase _PATTERN_PATH_EXT:j := strings.LastIndex(url, ".")if j > -1 {params[":path"] = url[:j]params[":ext"] = url[j+1:]} else {params[":path"] = url}return t.leaves[i].handle, truecase _PATTERN_HOLDER: params[t.leaves[i].wildcards[0]] = urlreturn t.leaves[i].handle, truecase _PATTERN_MATCH_ALL:params["*"] = urlparams["*"+com.ToStr(globLevel)] = urlreturn t.leaves[i].handle, true}}return nil, false
}
  • 叶子节点_PATTERN_MATCH_ALL的匹配过程

    假设注册路由为/api/v1/*,前端传入参数为/api/v1/hello,那么 params["*"]=hello

    假设注册路由为/api/v1/*/*/hello,前端传入参数为/api/v1/h1/h2/hello,那么 params["*0"]=h1,params["*1"]=h2,当然后面的hello也要匹配,如果不匹配就返回false了,传入的globLevel就是*这个后面的数字

  • 叶子节点_PATTERN_HOLDER的匹配过程

    假设注册路由为/api/v1/:id,前端传入参数为/api/v1/hello,那么t.leaves[i].wildcards[0]=”:id",params[t.leaves[i].wildcards[0]] =hello

  • 叶子节点 _PATTERN_PATH_EXT的匹配过程

    假设注册路由为/api/v1/*.*,前端传入参数为/api/v1/xxx.go,j为“.”在字符串的索引位置,那么结果就如下

    params[":path"] = xxx params[":ext"] = go

  • 叶子节点 _PATTERN_REGEXP的匹配过程

    先从url 里面找到匹配的结果,如果通配符数量和找到的结果不一致就break,继续下一次匹配,如果一致的话就循环赋值

    举例

    /user/:username([\w]+)==> /user/hello1 匹配:username=hello1

    /user/:id([0-9]+)==> /user/1 匹配:id=1

    /cms_:id([0-9]+).html ==> /user/4.html 匹配 :id=4

  • 叶子节点_PATTERN_STATIC的匹配过程

    当是普通路由匹配直接==判断就行了

匹配子树

func (t *Tree) matchSubtree(globLevel int, segment, url string, params Params) (Handle, bool) {unescapedSegment, err := PathUnescape(segment)if err != nil {return nil, false}for i := 0; i < len(t.subtrees); i++ {switch t.subtrees[i].typ {case _PATTERN_STATIC:if t.subtrees[i].pattern == unescapedSegment {if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {return handle, true}}case _PATTERN_REGEXP:results := t.subtrees[i].reg.FindStringSubmatch(unescapedSegment)if len(results)-1 != len(t.subtrees[i].wildcards) {break}for j := 0; j < len(t.subtrees[i].wildcards); j++ {params[t.subtrees[i].wildcards[j]] = results[j+1]}if handle, ok := t.subtrees[i].matchNextSegment(globLevel, url, params); ok {return handle, true}case _PATTERN_HOLDER:if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {params[t.subtrees[i].wildcards[0]] = unescapedSegmentreturn handle, true}case _PATTERN_MATCH_ALL:if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {params["*"+com.ToStr(globLevel)] = unescapedSegmentreturn handle, true}}}if len(t.leaves) > 0 {leaf := t.leaves[len(t.leaves)-1]unescapedURL, err := PathUnescape(segment + "/" + url)if err != nil {return nil, false}if leaf.typ == _PATTERN_PATH_EXT {j := strings.LastIndex(unescapedURL, ".")if j > -1 {params[":path"] = unescapedURL[:j]params[":ext"] = unescapedURL[j+1:]} else {params[":path"] = unescapedURL}return leaf.handle, true} else if leaf.typ == _PATTERN_MATCH_ALL {params["*"] = unescapedURLparams["*"+com.ToStr(globLevel)] = unescapedURLreturn leaf.handle, true}}return nil, false
}
  • 匹配子树的过程,跟叶子节点一样,循环遍历路径每一段,先匹配子树,到最后再匹配叶子节点

  • 匹配子树会走matchNextSegment的逻辑也就是递归调用

匹配成功执行handle

再来看看之前调到的handle,创建context,每个web框架都有这个东西,执行c.run(),预知后事如何,请看下面中间件分析。

func(resp http.ResponseWriter, req *http.Request, params Params) {c := r.m.createContext(resp, req)c.params = paramsc.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))c.handlers = append(c.handlers, r.m.handlers...)//添加全局默认handlec.handlers = append(c.handlers, handlers...) //添加用户路由的handlc.run()
}

关于匹配优先级

官方给出了如下的优先级,从上面匹配的case 来看,确实就是就这么执行匹配过程的

Matching priority of different match patterns from higher to lower:

  • Static routes:

    • /

    • /home

  • Regular expression routes:

    • /(.+).html

    • /([0-9]+).css

  • Path-extension routesL

    • /*.*

  • Placeholder routes:

    • /:id

    • /:name

  • Glob routes:

    • /*

Other notes:

  • Matching priority of same pattern is first add first match.

  • More detailed pattern gets higher matching priority:

    • /*/*/events > /*

为什么/*/*/events > /*,从代码里看,在匹配过程中,一直没到最后都是匹配子树,到最后解析到events才是叶子节点,所以优先级最高,功能还是比其他框架的路由匹配强大的。

如果取出匹配到的参数呢?

context有个Params方法,结果就会从前面解析的参数里面取出参数来

// Params returns value of given param name.
// e.g. ctx.Params(":uid") or ctx.Params("uid")
func (ctx *Context) Params(name string) string {if len(name) == 0 {return ""}if len(name) > 1 && name[0] != ':' {name = ":" + name}return ctx.params[name]
}

Context

context 跟其他框架使用方法大同小异,实现的函数方法都基本差不多,看来框架也很卷φ(>ω<*)

总结

  • macaron 的路由不是标准的trie 树,将会按照路径,两个/path1/path2中间的path为一个树的节点,而最后一个path是叶子节点,因此支持的功能和匹配还是挺强大的。

  • 匹配过程就是将传进来的url分段进行匹配,在匹配中解析参数,整个过程是一个递归,退出条件就是匹配到叶子节点,此时所有参数已经匹配好了,如果没有匹配到,说明是没有注册的路由,此时应该响应notfound,这个notfound 可以自己定义。

参考:

js正则表达式(.+)和(.+?) (.)和(.?)的区别


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部