深入解析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.pattern
            h = append(h, g.handlers...)
        }
​
        pattern = groupPattern + pattern
        h = 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 = params
        c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))
        c.handlers = append(c.handlers, r.m.handlers...)//添加全局默认handle
        c.handlers = append(c.handlers, handlers...) //添加用户路由的handl
        c.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.parent
		if parent.parent != nil {
			parent.parent.addLeaf(parent.pattern, handle) //添加到祖父节点
		} else {
			parent.addLeaf("", handle) // Root tree can add as empty pattern.
		}
	}
 
	i := 0
	for ; 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 := false
	if 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 := 0
	for ; 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_REGEXP
      pattern, 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/:id
	fmt.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]+).html

	fmt.Println(getNextWildcard("/:int")) //  结果为 :int /(.+)

	fmt.Println(getNextWildcard("/hello/*")) //  结果为 /hello/*
	fmt.Println(getNextWildcard("/date/*/*/*/events")) //  结果为  /date/*/*/*/events
	fmt.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 = 'aaa<div style="font-color:red;">123456</div>bbb'

<.+?>会匹配<div style="font-color:red;">

<.+>会匹配<div style="font-color:red;">123456</div>

路由匹配

首先来看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 routes
      leaf := 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 main

import (
	"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/z
	fmt.Println("RawPath:", u.RawPath)// /x/y%2Fz
	fmt.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: //如果是静态类型则直接匹配,然后返回true
         if 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, true
      case _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, true
      case _PATTERN_HOLDER: 
         params[t.leaves[i].wildcards[0]] = url
         return t.leaves[i].handle, true
      case _PATTERN_MATCH_ALL:
         params["*"] = url
         params["*"+com.ToStr(globLevel)] = url
         return 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]] = unescapedSegment
            return handle, true
         }
      case _PATTERN_MATCH_ALL:
         if handle, ok := t.subtrees[i].matchNextSegment(globLevel+1, url, params); ok {
            params["*"+com.ToStr(globLevel)] = unescapedSegment
            return 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["*"] = unescapedURL
         params["*"+com.ToStr(globLevel)] = unescapedURL
         return 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 = params
   c.handlers = make([]Handler, 0, len(r.m.handlers)+len(handlers))
   c.handlers = append(c.handlers, r.m.handlers...)//添加全局默认handle
   c.handlers = append(c.handlers, handlers...) //添加用户路由的handl
   c.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正则表达式(.+)和(.+?) (.)和(.?)的区别

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值