• 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是怎么工作的,源码走起

    beego.AutoRouter()
    func AutoRouter(c ControllerInterface) *App {
        BeeApp.AutoRouter(c)
        return BeeApp
    }
    此处调用App的AutoRouter方法,如下:
    func (app *App) AutoRouter(c ControllerInterface) *App {
        app.Handlers.AddAuto(c)
        return app
    }
    看下App的struct
    type App struct {
        Handlers *ControllerRegistor
    }
    可见app.Handlers就是ControllerRegistor,来看看ControllerRegistor的AddAuto方法
    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
        }
    }
    这个可以说就是智能路由的关键了,它充分利用率Golang的反射(reflect)机制。
    看看这个方法都为ControllerRegistor做了什么呢?先来看看ControllerRegistor的struct
    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
    }
    AddAuto方法首先将ControllerRegistor的enableAuto设置为true(具体作用稍后介绍)
    然后对传入的控制器做反射,rt := reflectVal.Type() 获取传入控制器的Type
    firstParam := strings.ToLower(strings.TrimSuffix(ct.Name(), "Controller")) 可以看出自动路由定义时,命名必须为XxxxController格式,否则是无法解析映射到路由上的。
    if _, ok := p.autoRouter[firstParam]; ok { return } else { p.autoRouter[firstParam] = make(map[string]reflect.Type) }
    这里如果autoRouter这个map里已经有firstParam这个键的时候,就不在映射,所以后定义的同名router是无法覆盖前面已经定义了的。
    这里autoRouter是一个二维map,传入一个UserController通过
    for i := 0; i < rt.NumMethod(); i++ { p.autoRouter[firstParam][rt.Method(i).Name] = ct }
    处理之后,autoRouter会新增autoRouter[“user”][“func1”],autoRouter[“user”][“func2”]….其中func1,func2即为UserController的全部方法。
    通过AddAuto方法我们得到了包含所有AutoRouter的一个map,即autoRouter,那么怎么样将这个map注册到路由里呢?
    继续八源码,在router.go文件中,为ControllerRegistor定义了一个ServeHttp的方法,这个方法比较长,愚安摘取相关的代码贴出来:
    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/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    BUGFREE安装等
    常用网站
    Mongodb
    python资源
    HTTP协议详解(经典)
    Jmeter工具
    一.移动app测试与质量保证
    我发现涯哥特有才。各种跳,组合式
    原来如此
    我真庆幸我看过那本书。
  • 原文地址:https://www.cnblogs.com/yuan-shuai/p/3301357.html
Copyright © 2020-2023  润新知