瞭解-路由及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()
}
...
}