介绍
祝大家中秋假日快乐!(~ ̄▽ ̄)~, 连续肝了三篇文章剖析了我经常使用的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 可以自己定义。