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()
    }
    ...
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章