beego源码分析(三)——了解

了解-路由及controller的处理过程

在初探和起源中我们知道了基本原理、初始化及启动时的处理,本节主要探究controller的大致处理过程。

一、系统路由及controller func信息的存储大致过程

在初探里我们列出了各种路由的调用过程,最终均会调用p.AddToRouter。因此我们先根据其中特殊的几个了解下详细的过程。

1.Get-基础路由

beego.Get->BeeApp.Handlers.Get->p.AddMethod->p.addToRouter

我们先了解下AddMethod,具体注释在行尾:

func (p *ControllerRegister) AddMethod(method, pattern string, f FilterFunc) {
    method = strings.ToUpper(method)
    if _, ok := HTTPMETHOD[method]; method != "*" && !ok {//剔除不支持的请求方式
        panic("not support http method: " + method)
    }
    route := &ControllerInfo{}
    route.pattern = pattern//设置路由
    route.routerType = routerTypeRESTFul//restful router类型
    route.runFunction = f//设置处理方法
    methods := make(map[string]string)
    if method == "*" {//如果是全匹配,添加所有请求方式
        for _, val := range HTTPMETHOD {
            methods[val] = val
        }
    } else {//否则只添加指定请求方式
        methods[method] = method
    }
    route.methods = methods
    for k := range methods {//遍历请求方式
        if k == "*" {//如果全匹配,遍历添加所有请求方式m、pattern及route到Tree上
            for _, m := range HTTPMETHOD {
                p.addToRouter(m, pattern, route)
            }
        } else {//否则只添加指定请求方式k、pattern及route到Tree上
            p.addToRouter(k, pattern, route)
        }
    }
}

2.Router-固定/正则/自定义方法路由

beego.Router->BeeApp.Handlers.Add->p.addWithMethodParams->p.addToRouter

我们先了解下addWithMethodParams,具体注释在行尾:

//调用到此处时methodParams为nil
func (p *ControllerRegister) addWithMethodParams(pattern string, c ControllerInterface, methodParams []*param.MethodParam, mappingMethods ...string) {
    reflectVal := reflect.ValueOf(c)
    t := reflect.Indirect(reflectVal).Type()//获取controller指向值的类型,后期匹配后会反射获取其对象
    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() {//反射获取指定controller内的方法是否存在
                        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//默认beego类型router
    route.controllerType = t//设置controller的类型
    route.methodParams = methodParams//nil
    if len(methods) == 0 {//未指定方法时
        for _, m := range HTTPMETHOD {
            p.addToRouter(m, pattern, route)
        }
    } else {
        for k := range methods {//指定方法时
            if k == "*" {//通配符,需要添加所有的请求方式到router
                for _, m := range HTTPMETHOD {
                    p.addToRouter(m, pattern, route)
                }
            } else {//添加指定的请求方式到router
                p.addToRouter(k, pattern, route)
            }
        }
    }
}

以上代码提到Router的用法有两种,①指定单个处理的controller,②指定多处理的controller的某个方法

3.AutoRouter-自动路由

beego.AutoRouter->BeeApp.Handlers.AddAuto->p.AddAutoPrefix->p.addToRouter

我们先了解下AddAutoPrefix,具体注释在行尾:

//调用到此处时prefix为"/"
func (p *ControllerRegister) AddAutoPrefix(prefix string, c ControllerInterface) {
    reflectVal := reflect.ValueOf(c)
    rt := reflectVal.Type()//获取类型
    ct := reflect.Indirect(reflectVal).Type()//获取controller指向值的类型,后期匹配后会反射获取其对象
    controllerName := strings.TrimSuffix(ct.Name(), "Controller")//获取controller的名称前缀
    for i := 0; i < rt.NumMethod(); i++ {//遍历可导出方法集
        if !utils.InSlice(rt.Method(i).Name, exceptMethod) {//排除beego自有方法
            route := &ControllerInfo{}
            route.routerType = routerTypeBeego//默认beego类型router
            route.methods = map[string]string{"*": rt.Method(i).Name}//保存方法集,默认适合所有请求方式
            route.controllerType = ct//设置controller的类型
            pattern := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name), "*")//小写格式pattern并带通配符(/controllername/method/*)
            patternInit := path.Join(prefix, controllerName, rt.Method(i).Name, "*")//原有格式pattern并带通配符(/controllerName/Method/*)
            patternFix := path.Join(prefix, strings.ToLower(controllerName), strings.ToLower(rt.Method(i).Name))小写格式pattern(/controllername/method)
            patternFixInit := path.Join(prefix, controllerName, rt.Method(i).Name)//原有格式pattern(/controllerName/Method)
            route.pattern = pattern//默认pattern
            for _, m := range HTTPMETHOD {//添加所有请求方式到router中
                p.addToRouter(m, pattern, route)
                p.addToRouter(m, patternInit, route)
                p.addToRouter(m, patternFix, route)
                p.addToRouter(m, patternFixInit, route)
            }
        }
    }
}

4.Include-注解路由

beego.Include->BeeApp.Handlers.Include->p.addWithMethodParams->p.addToRouter

也调用到了p.addWithMethodParams,具体分析见指定路由1

鉴于这个是注解路由我们还是有必要了解下:

func (p *ControllerRegister) Include(cList ...ControllerInterface) {
    if BConfig.RunMode == DEV {//dev模式
        skip := make(map[string]bool, 10)
        for _, c := range cList {//遍历controller
            reflectVal := reflect.ValueOf(c)
            t := reflect.Indirect(reflectVal).Type()//获取controller指向值的类型,后期匹配后会反射获取其对象
            wgopath := utils.GetGOPATHs()//获取gopath
            if len(wgopath) == 0 {
                panic("you are in dev mode. So please set gopath")
            }
            pkgpath := ""
            for _, wg := range wgopath {
                wg, _ = filepath.EvalSymlinks(filepath.Join(wg, "src", t.PkgPath()))//包名完整路径
                if utils.FileExists(wg) {//判断文件是否存在
                    pkgpath = wg
                    break
                }
            }
            if pkgpath != "" {
                if _, ok := skip[pkgpath]; !ok {//是否处理过
                    skip[pkgpath] = true
                    parserPkg(pkgpath, t.PkgPath())//生成对应的controller文件
                }
            }
        }
    }
    for _, c := range cList {//遍历controller
        reflectVal := reflect.ValueOf(c)
        t := reflect.Indirect(reflectVal).Type()
        key := t.PkgPath() + ":" + t.Name()//拼凑controller路径,如test/controllers:TestController
        if comm, ok := GlobalControllerRouter[key]; ok {//从GlobalControllerRouter中获取
            for _, a := range comm {//遍历所有路由、控制器、参数、请求方式及处理方法
                p.addWithMethodParams(a.Router, c, a.MethodParams, strings.Join(a.AllowHTTPMethods, ",")+":"+a.Method)
            }
        }
    }
}

从以上几个路由的处理来看,我们知道在调用p.AddToRouter前,methods是按照请求方式、处理方法对应关系保存到route中。

5.Handler-自定义Handler

beego.Handler->BeeApp.Handlers.Handler->p.addToRouter

func (p *ControllerRegister) Handler(pattern string, h http.Handler, options ...interface{}) {
    route := &ControllerInfo{}
    route.pattern = pattern//设置路由
    route.routerType = routerTypeHandler//handler router累心
    route.handler = h//设置处理handler
    if len(options) > 0 {
        if _, ok := options[0].(bool); ok {//是否前缀匹配
            pattern = path.Join(pattern, "?:all(.*)")
        }
    }
    for _, m := range HTTPMETHOD {//遍历添加支持所有请求方式
        p.addToRouter(m, pattern, route)
    }
}

6.addToRouter

// method 请求方式
// pattern 路由
// r route信息(包含类型、方法集等信息)
func (p *ControllerRegister) addToRouter(method, pattern string, r *ControllerInfo) {
    if !BConfig.RouterCaseSensitive {
        pattern = strings.ToLower(pattern)
    }
    if t, ok := p.routers[method]; ok {//当前请求方式Tree是否存在
        t.AddRouter(pattern, r)//存在直接添加
    } else {
        t := NewTree()//不存在新建Tree再添加
        t.AddRouter(pattern, r)
        p.routers[method] = t//最后保存至routers中
    }
}

至此,所有的路由信息均保存至p.routers中,即BeeApp.Handler.routers中。关于路由t.AddRouter的具体细节我们稍后的章节再来研究。

二、请求路由与controller的处理大致过程

我们在初探中提过,http请求最终会交由*ControllerRegister的ServeHTTP来处理,ServeHTTP是一个很长很长的func,我们在这里只关注重点的部分,同样关于匹配的细节此处不深究。

func (p *ControllerRegister) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
    ...
    // User can define RunController and RunMethod in filter
    if context.Input.RunController != nil && context.Input.RunMethod != "" {
        findRouter = true
        runMethod = context.Input.RunMethod
        runRouter = context.Input.RunController
    } else {
        routerInfo, findRouter = p.FindRouter(context)//根据请求路由查找routerInfo,此routerInfo即是我们在第一步存入的route
    }
    ...
    if routerInfo != nil {//如果查找到routerInfo
        //store router pattern into context
        context.Input.SetData("RouterPattern", routerInfo.pattern)
        //根据routerType处理
        if routerInfo.routerType == routerTypeRESTFul {//restful类型如beego.Get
            if _, ok := routerInfo.methods[r.Method]; ok {//匹配请求方式,匹配后直接交由runFunction处理
                isRunnable = true
                routerInfo.runFunction(context)
            } else {
                exception("405", context)
                goto Admin
            }
        } else if routerInfo.routerType == routerTypeHandler {//自定义Handler类型,如beego.Handler
            isRunnable = true
            routerInfo.handler.ServeHTTP(rw, r)//直接交由handler处理
        } else {//其他类型,主要是routerTypeBeego,如beego.Router、beego.AutoRouter、beego.Include、beego.AddNamespace等
            runRouter = routerInfo.controllerType//获取controller类型
            methodParams = routerInfo.methodParams//获取方法参数
            method := r.Method//获取请求方式
            if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPost {
                method = http.MethodPut
            }
            if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete {
                method = http.MethodDelete
            }
            //获取存储在map中请求方式对应的方法名
            if m, ok := routerInfo.methods[method]; ok {
                runMethod = m
            } else if m, ok = routerInfo.methods["*"]; ok {
                runMethod = m
            } else {
                runMethod = method
            }
        }
    }

    // also defined runRouter & runMethod from filter
    if !isRunnable {
        //Invoke the request handler
        vc := reflect.New(runRouter)//反射获取controller对象
        execController, ok := vc.Interface().(ControllerInterface)//转为ControllerInterface类型,以方便调用以下接口
        if !ok {
            panic("controller is not ControllerInterface")
        }
        ...
        if !context.ResponseWriter.Started {//未响应时,正常我们的controller命名的func名称不会与请求方式名称一样,如果一样的话就会交由下面报错处理
            //exec main logic
            switch runMethod {
            case http.MethodGet:
                execController.Get()
            case http.MethodPost:
                execController.Post()
            case http.MethodDelete:
                execController.Delete()
            case http.MethodPut:
                execController.Put()
            case http.MethodHead:
                execController.Head()
            case http.MethodPatch:
                execController.Patch()
            case http.MethodOptions:
                execController.Options()
            default://与请求方式不一样时
                if !execController.HandlerFunc(runMethod) {
                    method := vc.MethodByName(runMethod)//从controller对象中根据方法名反射获取方法
                    in := param.ConvertParams(methodParams, method.Type(), context)
                    out := method.Call(in)//调用方法

                    //For backward compatibility we only handle response if we had incoming methodParams
                    if methodParams != nil {
                        p.handleParamResponse(context, execController, out)
                    }
                }
            }
        ...
        // finish all runRouter. release resource
        execController.Finish()
    }
    ...
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值