Beego源码解析之Router
初始化控制器
func NewControllerRegister() *ControllerRegister {
cr := &ControllerRegister{
routers: make(map[string]*Tree),
policies: make(map[string]*Tree),
}
cr.pool.New = func() interface{} {
return beecontext.NewContext()
}
return cr
}
ServeHttp(rw http.ResponseWriter, r *http.Request)
ServeHttp实现/Go/src/net/http/server.go中Handler接口,一个handler表示一个http request,所以调用一次ServerHttp表示一个请求。ServerHttp需将回复的headers和data写回ResponseWriter,然后回传(FIX)。
context := p.pool.Get().(*beecontext.Context) //从 ControllerRegister中的pool对象池获得一个 Context对象
context.Reset(rw, r) // reset为重新初始化context里的对象,详见beego_code_context.md
参数配置
context.Output.EnableGzip = BConfig.EnableGzip
if BConfig.RunMode == DEV {
context.Output.Header("Server", BConfig.ServerName)
}
if !BConfig.RouterCaseSensitive {
urlPath = strings.ToLower(urlPath)
}
寻找静态路径前,会根据过滤函数,对url进行验证,判断是否符合请求规范
// filter for static file
if len(p.filters[BeforeStatic]) > 0 && p.execFilter(context, urlPath, BeforeStatic) {
goto Admin
}
请求服务器静态文件,如果找到请求对应的静态文件写入ResponseWriter,并把ResponseWriter.Started设为true,该方法只对Method为GET和HEAD适用
serverStaticRouter(context)
if context.ResponseWriter.Started {
findRouter = true
goto Admin
}
if r.Method != http.MethodGet && r.Method != http.MethodHead {
if BConfig.CopyRequestBody && !context.Input.IsUpload() {
context.Input.CopyBody(BConfig.MaxMemory)
}
context.Input.ParseFormOrMulitForm(BConfig.MaxMemory)
}
然后初始化session(如果beego config有开session),寻找路由之前进行路由过滤。
如果Input.RunController不为空且Input.RunMethod不为空,则设定findRouter = true,没找到的话通过FindRouter找request的url对应的路由是match哪个控制器的方法,如果通过路由树都没找到就回传404。
找到路由后,routerInfo的机构体。routerInfo是一个控制器的结构体,记录这个如有的方法(???为什么是数组而不是一个)及对应控制器的相关属性。
type ControllerInfo struct {
pattern string
controllerType reflect.Type
methods map[string]string
handler http.Handler
runFunction FilterFunc
routerType int
initialize func() ControllerInterface
methodParams []*param.MethodParam
}
if context.Input.RunController != nil && context.Input.RunMethod != "" {
findRouter = true
runMethod = context.Input.RunMethod
runRouter = context.Input.RunController
} else {
routerInfo, findRouter = p.FindRouter(context)
}
if !findRouter {
exception("404", context)
goto Admin
}
找到路由之后,绑定路径参数到context.Input内
if splat := context.Input.Param(":splat"); splat != "" {
for k, v := range strings.Split(splat, "/") {
context.Input.SetParam(strconv.Itoa(k), v)
}
}
执行之前进行中间件过滤及查看policy
if len(p.filters[BeforeExec]) > 0 && p.execFilter(context, urlPath, BeforeExec) {
goto Admin
}
if p.execPolicy(context, urlPath) {
goto Admin
}
找到路由的判断方式是,上面的判断上面routerInfo是否为nil,如果不为nil则通过判断routerInfo.routerType来决定要怎么往下执行。有三种routerType:
routerTypeBeego: 0
routerTypeRESTFul:1
routerTypeHandler: 2
如果是routerTypeRESTFul,判断request的method是否和请求routerInfo这个方法一致,如果一致则直接执行routerInfo的runFunction。否则回传405,Method Not Allowed;
如果是routerTypeHandler,则执行context的handler里的ServeHTTP function;
如果上述两种皆非,会通过routerInfo里的控制器结构,得到请求对应的控制器对象信息,请求方法及请求参数;
if routerInfo != nil {
context.Input.SetData("RouterPattern", routerInfo.pattern)
if routerInfo.routerType == routerTypeRESTFul {
if _, ok := routerInfo.methods[r.Method]; ok {
isRunnable = true
routerInfo.runFunction(context)
} else {
exception("405", context)
goto Admin
}
} else if routerInfo.routerType == routerTypeHandler {
isRunnable = true
routerInfo.handler.ServeHTTP(rw, r)
} else {
runRouter = routerInfo.controllerType
methodParams = routerInfo.methodParams
method := r.Method
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodPost { //旧版的判断式http.MethodPut而不是http.MethodPost
method = http.MethodPut
}
if r.Method == http.MethodPost && context.Input.Query("_method") == http.MethodDelete {
method = http.MethodDelete
}
if m, ok := routerInfo.methods[method]; ok {
runMethod = m
} else if m, ok = routerInfo.methods["*"]; ok {
runMethod = m
} else {
runMethod = method
}
}
}
最后一个else里并没有正常执行控制器里的对应runFunction,所以在接下来的if(!isRunable)里会去尝试匹配request对应的控制器的方法。
首先会对找到的控制器对象初始化,调控制器对象的initialize function。如果没有对应的initialize function,则通过反射获得当初注册路由时对应的控制器对象。调取函数初始化需要被调用的控制器对象。
如果控制器对象有实现URLMapping function,则在调用URLMapping时执行request要调用的function。如果没有实现(beego默认该function没有做事情),表明context.ResponseWriter.Started = false,则需switch匹配request method对应的function。
如果没有在上一步没有执行对应的function,并且context.Output.Started为false,会去看该function是否是需要回传模版文件,即执行execController.render()。
最后执行execController.Finish(),执行完调用的function后才能执行Finish function。
if !isRunnable {
var execController ControllerInterface
if routerInfo.initialize != nil {
execController = routerInfo.initialize()
} else {
vc := reflect.New(runRouter)
var ok bool
execController, ok = vc.Interface().(ControllerInterface)
if !ok {
panic("controller is not ControllerInterface")
}
}
execController.Init(context, runRouter.Name(), runMethod, execController)
execController.Prepare()
// 是否开启Cross-site request forgery
if BConfig.WebConfig.EnableXSRF {
execController.XSRFToken()
if r.Method == http.MethodPost || r.Method == http.MethodDelete || r.Method == http.MethodPut ||
(r.Method == http.MethodPost && (context.Input.Query("_method") == http.MethodDelete || context.Input.Query("_method") == http.MethodPut)) {
execController.CheckXSRFCookie()
}
}
execController.URLMapping()
if !context.ResponseWriter.Started {
//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) {
vc := reflect.ValueOf(execController)
method := vc.MethodByName(runMethod)
in := param.ConvertParams(methodParams, method.Type(), context)
out := method.Call(in)
if methodParams != nil {
p.handleParamResponse(context, execController, out)
}
}
}
//render template
if !context.ResponseWriter.Started && context.Output.Status == 0 {
if BConfig.WebConfig.AutoRender {
if err := execController.Render(); err != nil {
logs.Error(err)
}
}
}
}
// finish all runRouter. release resource
execController.Finish()
}
记录请求信息
Admin:
//admin module record QPS
statusCode := context.ResponseWriter.Status
if statusCode == 0 {
statusCode = 200
}
logAccess(context, &startTime, statusCode)
if BConfig.Listen.EnableAdmin {
timeDur := time.Since(startTime)
pattern := ""
if routerInfo != nil {
pattern = routerInfo.pattern
}
if FilterMonitorFunc(r.Method, r.URL.Path, timeDur, pattern, statusCode) {
if runRouter != nil {
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, runRouter.Name(), timeDur)
} else {
go toolbox.StatisticsMap.AddStatistics(r.Method, r.URL.Path, "", timeDur)
}
}
}
if BConfig.RunMode == DEV && !BConfig.Log.AccessLogs {
var devInfo string
timeDur := time.Since(startTime)
iswin := (runtime.GOOS == "windows")
statusColor := logs.ColorByStatus(iswin, statusCode)
methodColor := logs.ColorByMethod(iswin, r.Method)
resetColor := logs.ColorByMethod(iswin, "")
if findRouter {
if routerInfo != nil {
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s r:%s", context.Input.IP(), statusColor, statusCode,
resetColor, timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path,
routerInfo.pattern)
} else {
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
timeDur.String(), "match", methodColor, r.Method, resetColor, r.URL.Path)
}
} else {
devInfo = fmt.Sprintf("|%15s|%s %3d %s|%13s|%8s|%s %-7s %s %-3s", context.Input.IP(), statusColor, statusCode, resetColor,
timeDur.String(), "nomatch", methodColor, r.Method, resetColor, r.URL.Path)
}
if iswin {
logs.W32Debug(devInfo)
} else {
logs.Debug(devInfo)
}
}
// Call WriteHeader if status code has been set changed
if context.Output.Status != 0 {
context.ResponseWriter.WriteHeader(context.Output.Status)
}