上一篇文章Beego源码解析(一)-配置项初始化流程 介绍了 Beego关于配置项初始化的流程。那么今天就来说说在 Beego中非常重要的路由机制 . Beego到现在 v1.6.1版本为止支持了:固定路由 、正则路由 、自动路由 这三种路由方法. 关于这三种路由的详细用法可以参考官方给出的开发文档 ,这里面已经记录的很全面了.
所以我们今天这篇文章就是要介绍这三种路由是如何在 Beego内部实现的.
关于 Beego的源码注释可以见我的Github
一个简单的示例 让我们先从官网给出的示例开始,下面是会在浏览器中打印”HelloWorld”的一个Beego程序.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package mainimport ( "github.com/astaxie/beego" ) type MainController struct { beego.Controller } func (this *MainController) Get() { this.Ctx.WriteString("Hello World" ) } func main () { beego.Router("/" ,&MainController{}) beego.Run() }
我们需要先知道它干了什么:
自定义了一个内含 beego.Controller(这个类型后面会讲到)控制器的 MainController
重写了 MainController的 Get()方法,熟悉 Go语言的应该知道这个方法来自 Controller
在 main()函数中调用了 beego.Router()方法注册了路由”/“和一个 MainController实例
执行了 beego.Run()方法启用了 beego程序
重要的类型和接口 为了不在接下来的流程中打断,在介绍流程之前需要先了解 beego中关于路由的一些东西
ControllerInterface 接口 源文件中的位置: beego/controller.go:90
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type ControllerInterface interface { Init(ct *context.Context,controllerName,actionName string ,app interface {}) Prepare() Get() Post() Delete() Put() Head() Patch() Options() Finish() Render() error XSRFToken() string CheckXSRFCookie() bool HandlerFunc(fn string ) bool URLMapping() }
这个接口定义了 15个方法,看名字就能够知道这是每个 Controller都需要实现的接口
Controller结构体 位置 beego/controller.go:60
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 type Controller struct {Ctx *context.Context Data map [interface {}]interface {} controllerName string actionName string methodMapping map [string ]func () gotofunc string AppController interface {} TplName string Layout string LayoutSections map [string ]string TplExt string EnableRender bool _xsrfToken string XSRFExpire int EnableXSRF bool CruSession session.Store }
这个结构体保存了作为 Controller的一些必要的信息,一些基础的字段看名字就比较好理解 在这里的 context.Context(Beego中的上下文,封装了 HTTP的输入和输出)和 Session.Store(用于存储 Session)在以后的文章中会再提到
在这个源文件中的后面部分都是对 Controller的一些方法实现,我们会注意到 Controller实现了 ControllerInterface的方法,但是在一些方法实现中却是用 Ctx成员向客户端进行错误输出(例如 Get()方法)。 因为就像例子中给的一样,当我们需要自己定义 Controller,并且使用 Get()函数来完成对客户端 Get请求的处理时,我们就需要自己实现处理逻辑,这样就覆盖 了本身输出错误的方法.而对于我们没有实现的方法(比如例子中的 Post()方法)没有重写,则对于客户端的 Post请求就会输出错误了
ControllerRegister结构体 这是一个非常关键的数据结构,为什么说他关键呢?我们可以先看下 Beego中 App结构体的定义 位置: beego/app.go
1 2 3 4 type App struct { Handlers *ControllerRegister Server *http.Server }
关于 App需要说下,在程序中 App类型的变量BeeApp (beego/app.go:32)在 init()函数中会调用 NewApp()创建出唯一的一个Beego程序实例 可以看到在例子中 main()函数最后调用了 beego.Run()函数,这个函数会在设置完hooks(关于回调方法以后也会介绍)后进入 BeeApp.Run()函数并且在进入这个函数后就会根据配置项开始不同的 HTTP请求的处理(在 ControllerRegister实现的 ServeHTTP()方法中) App中一共就两个变量,一个Server (标准包中 http.Server类型,这个不做介绍,需要的可以看 Go语言文档). 另外一个就是 ControllerRegister ,这个 ControllerRegister顾名思义就是注册 Controller的管理器,那么如何管理的呢?接下来看定义
位置: beego/router.go:115
1 2 3 4 5 6 type ControllerRegister struct { routers map [string ][]*Tree enableFilter bool filters map [int ][]*FilterRouter pool sync.Pool }
可以看到这篇文章的主角已经出现了, routers就是我们程序运行时所需要的路由表, routers的 key是我们注册的方法名(例如”get”、”post”等),而 value就是由注册的路由构建出来的路由树了(关于路由树,后面也会讲到).
ControllerInfo结构体 这个结构体是用来保存我们自定义的控制器信息的,看下定义便知道 位置: beego/router.go:104
1 2 3 4 5 6 7 8 type ControllerInfo struct { pattern string controllerType reflect.Type methods map [string ]string handler http.Handler runFunction FilterFunc routerType int }
Tree结构体 1 2 3 4 5 6 7 8 9 10 type Tree struct { prefix string fixrouters []*Tree wildcard *Tree leaves []*leafInfo }
leafInfo结构体 1 2 3 4 5 type leafInfo struct { wildcards []string regexps *regexp.Regexp runObject interface {} }
这两个结构体就会构成一颗用来查找路由的路由树,通过结构体的定义我们应该不难看出关于正则的部分都会存放在叶子节点中.所以这个路由树的大致样子也比较好理解
路由的注册过程 在前面的实例中可以看到需要注册自己的 Controller时使用的是 beego.Router()函数(在官方开发文档中的基础路由部分也可以使用 beego.Get()方法注册路由,不过内部与用 beego.Router()注册方法相比都会使用 addToRouter()函数,所以也是比较相似的)
看下 beego.Router的原型:
1 2 3 4 5 beego/app.go :211 func Router (rootpath string ,c ControllerInterface,mappingMethods ...string *App) { BeeApp.Handlers.Add(rootpath,c,mappingMethods...) return BeeApp }
看到第一个参数是需要注册路由,而第二个参数是我们自定义实现了 ControllerInterface接口的控制器,第三个就是自定义路由中方法和处理函数的映射关系 函数内部实际调用了 App.ControllerRegister的Add()方法来注册 接下来看看 Add()方法做了什么: 位置: beego/router.go:144
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 func (p *ControllerRegister) Add(pattern string , c ControllerInterface, mappingMethods ...string ) { reflectVal := reflect.ValueOf(c) t := reflect.Indirect(reflectVal).Type() methods := make (map [string ]string ) if len (mappingMethods) > 0 { semi := strings.Split(mappingMethods[0 ], ";" ) for _, v := range semi { colon := strings.Split(v, ":" ) if len (colon) != 2 { panic ("method mapping format is invalid" ) } comma := strings.Split(colon[0 ], "," ) for _, m := range comma { if _, ok := HTTPMETHOD[strings.ToUpper(m)]; m == "*" || ok { if val := reflectVal.MethodByName(colon[1 ]); val.IsValid() { methods[strings.ToUpper(m)] = colon[1 ] } else { panic ("'" + colon[1 ] + "' method doesn't exist in the controller " + t.Name()) } } else { panic (v + " is an invalid method mapping. Method doesn't exist " + m) } } } } route := &controllerInfo{} route.pattern = pattern route.methods = methods route.routerType = routerTypeBeego route.controllerType = t if len (methods) == 0 { for _, m := range HTTPMETHOD { p.addToRouter(m, pattern, route) } } else { for k := range methods { if k == "*" { for _, m := range HTTPMETHOD { p.addToRouter(m, pattern, route) } } else { p.addToRouter(k, pattern, route) } } } }
这是一个稍微长点的函数,不过通过注释可以看出这个函数做了几个工作:
解析了传入的 mappingMethods,得到其中包含的全部方法
用传入的4个参数构造出一个 ControllerInfo的实例,而这个实例中就保存了我们自定的控制器的 reflct.Type类型(可参考ControllerInfo )
在函数的最后调用了 ControllerRegister的 addToRouter()方法
位置: beego/router.go:199
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func (p *ControllerRegister) addToRouter(method, pattern string , r *controllerInfo) { if !BConfig.RouterCaseSensitive { pattern = strings.ToLower(pattern) } if t, ok := p.routers[method]; ok { t.AddRouter(pattern, r) } else { t := NewTree() t.AddRouter(pattern, r) p.routers[method] = t } }
这个方法比较短,主要是判断当前的方法是否在 ControllerRegister的成员 routers所支持的方法中
存在就直接插入对应的路由树
否则创建一个新的路由树
路由树节点的插入操作就是 Tree.AddRouter()方法
位置: beego/tree.go:206
1 2 3 func (t *Tree) AddRouter(pattern string ,runObject interface {}) { t.addseg(splitPath(pattern),runObject,nil ,"" ) }
可以看出它只是把 pattern中的路径进行了切割(例如”/admin/users”切割成”[“admin”,”users”]”),并返回一个 string类型的数组切片 那么接下来的目的就很明确了,我们需要使用 Tree提供的 addseg方法给路由树添加节点
这个函数也是最终的一个函数了,函数的逻辑可以看注释
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 func (t *Tree) addseg(segments []string , route interface {}, wildcards []string , reg string ) { if len (segments) == 0 { if reg != "" { t.leaves = append (t.leaves, &leafInfo{runObject: route, wildcards: wildcards, regexps: regexp.MustCompile("^" + reg + "$" )}) } else { t.leaves = append (t.leaves, &leafInfo{runObject: route, wildcards: wildcards}) } } else { seg := segments[0 ] iswild, params, regexpStr := splitSegment(seg) if len (params) > 0 && params[0 ] == ":" { t.addseg(segments[1 :], route, wildcards, reg) params = params[1 :] } if !iswild && utils.InSlice(":splat" , wildcards) { iswild = true regexpStr = seg } if seg == "*" && len (wildcards) > 0 && reg == "" { regexpStr = "(.+)" } if iswild { if t.wildcard == nil { t.wildcard = NewTree() } if regexpStr != "" { if reg == "" { rr := "" for _, w := range wildcards { if w == ":splat" { rr = rr + "(.+)/" } else { rr = rr + "([^/]+)/" } } regexpStr = rr + regexpStr } else { regexpStr = "/" + regexpStr } } else if reg != "" { if seg == "*.*" { regexpStr = "/([^.]+).(.+)" params = params[1 :] } else { for range params { regexpStr = "/([^/]+)" + regexpStr } } } else { if seg == "*.*" { params = params[1 :] } } t.wildcard.addseg(segments[1 :], route, append (wildcards, params...), reg+regexpStr) } else { var subTree *Tree for _, sub := range t.fixrouters { if sub.prefix == seg { subTree = sub break } } if subTree == nil { subTree = NewTree() subTree.prefix = seg t.fixrouters = append (t.fixrouters, subTree) } subTree.addseg(segments[1 :], route, wildcards, reg) } } }
至此路由树节点添加完成
这里需要提一下 splitSegment这个函数 位置: beego/tree.go:489
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 func splitSegment (key string ) (bool , []string , string )
Final 最终我们从调用beego.Router()到最后给 ControllerRegister.router成功添加路由树节点的过程就完成了 总结一下就是注册路由的过程就是在添加 ControllerRegister中的路由树的节点,而在 HTTP执行的过程中对这棵树进行搜索(这就到树的搜索方法了),从而判断接受到的请求应该怎么样的处理(对应的根据 Controller不同的类型调用不同的方法) 完成 HTTP请求的正常处理过程:D
如果文章有误,非常希望能给我提出,好让我更正 :D