beego 0.9.0 中智能路由AutoRouter的使用方法及源码解读
了解beego的开发者肯定知道,beego的路由设计来源于sinatra,原来是不支持自动路由的,每一个路由都要自己配置的,如:
type MainController struct { beego.Controller } func (this *MainController) Get() { this.Ctx.WriteString("hello world") } func main() { beego.Router("/", &MainController{}) beego.Run() }
beego.Controller 提供所有的restful方法,Get,Post,Delete等方法,通过重写这些方法,已响应客户端不同的请求方式。
用过Node.js的同学,肯定觉得很熟悉,拿最常用的express框架来说,路由的定义方式也大抵是这样的,如:
app.get('/', index); var index = function(req,res){ res.send("Hello,express"); };
有的同学肯定觉得为什么像php,java,asp这种服务器语言就不需要这样定义路由,自然会根据请求url,判断脚本path,进项执行并返回。
其实这些也是需要的,只不过这个工作交给了服务器软件来解决,如Apache,Nginx,IIS,Tomcat等。由这些软件提供Http服务,脚本程序本身更专注于服务的逻辑。
而如Node.js,Golang这种语言,是由自生提供Http服务,并监听某一端口。所以通过查看服务器响应Header可以看出,此类语言的server显示为express,beegoserver,而大部分网站返回头的server为Nginx,Apache等。当然,Golang,Node.js也可以通过反向代理功能(如Nginx),使真正与客户端打交道的变为这些反向代理软件,但注意的是,这并不代表Node.js等的Http服务和路由调度不工作了,他们依然接受来自反向代理软件的Http请求,并作出响应。
好了,扯了这么多,那0.9.0版本的beego提供的智能路由究竟是怎样呢?
先看一段,示例代码:
package main import ( "github.com/astaxie/beego"
"myapp/beego/controllers"
) func main() { beego.AutoRouter(&controllers.UserController{}) beego.AutoRouter(&controllers.PageController{}) //........ beego.Run() }
控制器代码如下:
package controllers import ( "github.com/astaxie/beego" ) type UserController struct { beego.Controller } type PageController struct {
beego.Controller
} func (this *UserController) Add() { this.Ctx.WriteString("/user/add") }
func (this *PageController) About() {
this.Ctx.WriteString("/page/about")
}
有了这个AutoRouter,便不需要像以前那样逐一注册了,访问/user/add 调用UserController的Add方法,访问/page/about调用PageController的About方法。
这里需要稍微提醒两点:
1.控制器struct极其下func都必须以大写字母开头,因为在Golang里默认大写开头的为public,小写开头的为private,私有的内容无法被包外访问。
2.在使用了AutoRouter之后,原来的Router方法依然是有效的,可以继续使用。
好了,AutoRouter的使用就先介绍这里,0.9.0版本的beego还是更新和添加了不少功能的,在这里感谢Astaxie为golang项目所做的努力。
beego具体全面的使用,大家如果感兴趣的话,我以后可以抽个时间做个完成的介绍。
接下来我们来具体看看AutoRouter是怎么工作的,源码走起:
func AutoRouter(c ControllerInterface) *App { BeeApp.AutoRouter(c) return BeeApp }
func (app *App) AutoRouter(c ControllerInterface) *App { app.Handlers.AddAuto(c) return app }
type App struct { Handlers *ControllerRegistor }
func (p *ControllerRegistor) AddAuto(c ControllerInterface) { p.enableAuto = true reflectVal := reflect.ValueOf(c) rt := reflectVal.Type() ct := reflect.Indirect(reflectVal).Type() firstParam := strings.ToLower(strings.TrimSuffix(ct.Name(), "Controller")) if _, ok := p.autoRouter[firstParam]; ok { return } else { p.autoRouter[firstParam] = make(map[string]reflect.Type) } for i := 0; i < rt.NumMethod(); i++ { p.autoRouter[firstParam][rt.Method(i).Name] = ct } }
type ControllerRegistor struct { routers []*controllerInfo fixrouters []*controllerInfo enableFilter bool filters []http.HandlerFunc enableAfter bool afterFilters []http.HandlerFunc enableUser bool userHandlers map[string]*userHandler enableAuto bool autoRouter map[string]map[string]reflect.Type //key:controller key:method value:reflect.type }
if p.enableAuto { if !findrouter { for cName, methodmap := range p.autoRouter { if strings.ToLower(requestPath) == "/"+cName { http.Redirect(w, r, requestPath+"/", 301) return } if strings.ToLower(requestPath) == "/"+cName+"/" { requestPath = requestPath + "index" } if strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/") { for mName, controllerType := range methodmap { if strings.HasPrefix(strings.ToLower(requestPath), "/"+cName+"/"+strings.ToLower(mName)) { //execute middleware filters if p.enableFilter { for _, filter := range p.filters { filter(w, r) if w.started { return } } } //parse params otherurl := requestPath[len("/"+cName+"/"+strings.ToLower(mName)):] if len(otherurl) > 1 { plist := strings.Split(otherurl, "/") for k, v := range plist[1:] { params[strconv.Itoa(k)] = v } } //Invoke the request handler vc := reflect.New(controllerType) //call the controller init function init := vc.MethodByName("Init") in := make([]reflect.Value, 2) ct := &Context{ResponseWriter: w, Request: r, Params: params, RequestBody: requestbody} in[0] = reflect.ValueOf(ct) in[1] = reflect.ValueOf(controllerType.Name()) init.Call(in) //call prepare function in = make([]reflect.Value, 0) method := vc.MethodByName("Prepare") method.Call(in) method = vc.MethodByName(mName) method.Call(in) //if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf if EnableXSRF { method = vc.MethodByName("XsrfToken") method.Call(in) if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" || (r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) { method = vc.MethodByName("CheckXsrfCookie") method.Call(in) } } if !w.started { if AutoRender { method = vc.MethodByName("Render") method.Call(in) } } method = vc.MethodByName("Finish") method.Call(in) //execute middleware filters if p.enableAfter { for _, filter := range p.afterFilters { filter(w, r) if w.started { return } } } method = vc.MethodByName("Destructor") method.Call(in) // set find findrouter = true } } } } } }
这里可以看到最先有一个判断if !findrouter 即如果没有找到路由匹配,才会进行智能路由匹配,所以Router的优先级是比AutoRouter要高的。
在这里再次用到了reflect,这里 ct := &Context{ResponseWriter: w, Request: r, Params: params, RequestBody: requestbody} 即获取http请求上下文,通过method.Call(in),
将http请求参数传给Controller内的相对应的方法。
不难看出,做了多步处理,有点类似PHP的钩子(hooks),依次经过控制器init方法->Prepare->XsrfToken->Render->Finish->Destructor等处理。
在最后set findrouter 为true,如果在这里仍没有匹配到router,接下来就404了
if !findrouter { if h, ok := ErrorMaps["404"]; ok { w.status = 404 h(w, r) } else { http.NotFound(w, r) } }
所以beego的设计还是比较严谨且有效率的,在这里在此代表广大Golang初学者感谢谢大。
额,第一次写Golang的文章,感觉力不从心,说了一堆废话,忘园友们见谅!
作者:愚安
出处:http://www.cnblogs.com/yuan-shuai/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。