zoukankan      html  css  js  c++  java
  • Beego源码分析(转)

    摘要

    beego 是 @astaxie 开发的重量级Go语言Web框架。它有标准的MVC模式,完善的功能模块,和优异的调试和开发模式等特点。并且beego在国内企业用户较多,社区发达和Q群,文档齐全,特别是 @astaxie 本人对bug和issue等回复和代码修复很快,非常敬业。beego框架本身模块众多,无法简单描述所有的功能。我简单阅读了源码,记录一下beego执行过程。官方文档已经图示了beego执行过程图,而我会比较详细的解释beego的源码实现。

    beego 是 @astaxie 开发的重量级Go语言Web框架。它有标准的MVC模式,完善的功能模块,和优异的调试和开发模式等特点。并且beego在国内企业用户较多,社区发达和Q群,文档齐全,特别是 @astaxie 本人对bug和issue等回复和代码修复很快,非常敬业。beego框架本身模块众多,无法简单描述所有的功能。我简单阅读了源码,记录一下beego执行过程。官方文档已经图示了beego执行过程,而我会比较详细的解释beego的源码实现。 <!--more--> 注意,本文基于beego 1.1.4 (2014.04.15) 源码分析,且不是beego的使用教程。使用细节的问题在这里不会说明。

    本文小站地址:http://fuxiaohei.me/article/27/beego-source-study.html

    1. 启动应用

    beego官方首页提供的示例非常简单:

    package main
    
    import "github.com/astaxie/beego"
    
    func main() {
        beego.Run()
    }
    

    那么,从Run()方法开始,在beego.go#179

    func Run() {
        initBeforeHttpRun()
    
        if EnableAdmin {
            go beeAdminApp.Run()
        }
    
        BeeApp.Run()
    }
    

    额呵呵呵,还在更里面,先看initBeforeHttpRun(),在beego.go#L189:

    func initBeforeHttpRun() {
        // if AppConfigPath not In the conf/app.conf reParse config
        if AppConfigPath != filepath.Join(AppPath, "conf", "app.conf") {
            err := ParseConfig()
            if err != nil && AppConfigPath != filepath.Join(workPath, "conf", "app.conf") {
                // configuration is critical to app, panic here if parse failed
                panic(err)
            }
        }
    
        // do hooks function
        for _, hk := range hooks {
            err := hk()
            if err != nil {
                panic(err)
            }
        }
    
        if SessionOn {
            var err error
            sessionConfig := AppConfig.String("sessionConfig")
            if sessionConfig == "" {
                sessionConfig = `{"cookieName":"` + SessionName + `",` +
                    `"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime, 10) + `,` +
                    `"providerConfig":"` + SessionSavePath + `",` +
                    `"secure":` + strconv.FormatBool(HttpTLS) + `,` +
                    `"sessionIDHashFunc":"` + SessionHashFunc + `",` +
                    `"sessionIDHashKey":"` + SessionHashKey + `",` +
                    `"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` +
                    `"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}`
            }
            GlobalSessions, err = session.NewManager(SessionProvider,
                sessionConfig)
            if err != nil {
                panic(err)
            }
            go GlobalSessions.GC()
        }
    
        err := BuildTemplate(ViewsPath)
        if err != nil {
            if RunMode == "dev" {
                Warn(err)
            }
        }
    
        middleware.VERSION = VERSION
        middleware.AppName = AppName
        middleware.RegisterErrorHandler()
    }
    

    从代码看到在Run()的第一步,初始化AppConfig,调用hooks,初始化GlobalSessions,编译模板BuildTemplate(),和加载中间件middleware.RegisterErrorHandler(),分别简单叙述。

    1.1 加载配置

    加载配置的代码是:

    if AppConfigPath != filepath.Join(AppPath, "conf", "app.conf") {
        err := ParseConfig()
        if err != nil && AppConfigPath != filepath.Join(workPath, "conf", "app.conf") {
            // configuration is critical to app, panic here if parse failed
            panic(err)
        }
    }
    

    判断配置文件是不是AppPath/conf/app.conf,如果不是就ParseConfig()。显然他之前就已经加载过一次了。找了一下,在config.go#L152,具体加载什么就不说明了。需要说明的是AppPathworkPath这俩变量。找到定义config.go#72

    workPath, _ = os.Getwd()
    workPath, _ = filepath.Abs(workPath)
    // initialize default configurations
    AppPath, _ = filepath.Abs(filepath.Dir(os.Args[0]))
    
    AppConfigPath = filepath.Join(AppPath, "conf", "app.conf")
    
    if workPath != AppPath {
        if utils.FileExists(AppConfigPath) {
            os.Chdir(AppPath)
        } else {
            AppConfigPath = filepath.Join(workPath, "conf", "app.conf")
        }
    }
    

    workPathos.Getwd(),即当前的目录;AppPathos.Args[0],即二进制文件所在目录。有些情况下这两个是不同的。比如把命令加到PATH中,然后cd到别的目录执行。beego以二进制文件所在目录为优先。如果二进制文件所在目录没有发现conf/app.conf,再去workPath里找。

    1.2 Hooks

    hooks就是钩子,在加载配置后就执行,这是要做啥呢?在 beego.go#L173 添加新的hook:

    // The hookfunc will run in beego.Run()
    // such as sessionInit, middlerware start, buildtemplate, admin start
    func AddAPPStartHook(hf hookfunc) {
        hooks = append(hooks, hf)
    }
    

    hooks的定义在beego.go#L19

    type hookfunc func() error //hook function to run
    var hooks []hookfunc       //hook function slice to store the hookfunc
    

    hook就是func() error类型的函数。那么为什么调用hooks可以实现代码注释中的如middleware start, build template呢?因为beego使用的是单实例的模式。

    1.3 单实例

    beego的核心结构是beego.APP,保存路由调度结构*beego.ControllerRegistor。从beego.Run()方法的代码BeeApp.Run()发现,beego有一个全局变量BeeApp是实际调用的*beego.APP实例。也就是说整个beego就是一个实例,不需要类似NewApp()这样的写法。

    因此,很多结构都作为全局变量如beego.BeeApp暴露在外。详细的定义在 config.go#L18,特别注意一下SessionProvider(string),马上就要提到。

    1.4 会话 GlobalSessions

    继续beego.Run()的阅读,hooks调用完毕后,初始化会话GlobalSessions

    if SessionOn {
        var err error
        sessionConfig := AppConfig.String("sessionConfig")
        if sessionConfig == "" {
            sessionConfig = `{"cookieName":"` + SessionName + `",` +
                `"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime, 10) + `,` +
                `"providerConfig":"` + SessionSavePath + `",` +
                `"secure":` + strconv.FormatBool(HttpTLS) + `,` +
                `"sessionIDHashFunc":"` + SessionHashFunc + `",` +
                `"sessionIDHashKey":"` + SessionHashKey + `",` +
                `"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` +
                `"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}`
        }
        GlobalSessions, err = session.NewManager(SessionProvider,
            sessionConfig)
        if err != nil {
            panic(err)
        }
        go GlobalSessions.GC()
    }
    

    beego.SessionOn定义是否启动Session功能,然后sessionConfig是Session的配置,如果配置为空,就使用拼接的默认配置。sessionConfig是json格式。

    session.NewManager()返回*session.Manager,session的数据存储引擎是beego.SessionProvider定义,比如"file",文件存储。

    go GlobalSessions.GC()开启一个goroutine来处理session的回收。阅读一下GC()的代码,在session/session.go#L183

    func (manager *Manager) GC() {
        manager.provider.SessionGC()
        time.AfterFunc(time.Duration(manager.config.Gclifetime)*time.Second, func() { manager.GC() })
    }
    

    这是个无限循环time.AfterFunc()在经过一段时间间隔time.Duration(...)之后,又调用自己,相当于又开始启动time.AfterFunc()等待下一次到期。manager.provider.SessionGC()是不同session存储引擎的回收方法(其实是session.Provider接口的)。

    1.5 模板构建

    继续beego.Run(),session初始化后,构建模板:

    err := BuildTemplate(ViewsPath)
    

    beego.ViewsPath是模板的目录啦,不多说。仔细来看看BuildTemplate()函数,template.goL#114

    // build all template files in a directory.
    // it makes beego can render any template file in view directory.
    func BuildTemplate(dir string) error {
        if _, err := os.Stat(dir); err != nil {
            if os.IsNotExist(err) {
                return nil
            } else {
                return errors.New("dir open err")
            }
        }
        self := &templatefile{
            root:  dir,
            files: make(map[string][]string),
        }
        err := filepath.Walk(dir, func(path string, f os.FileInfo, err error) error {
            return self.visit(path, f, err)
        })
        if err != nil {
            fmt.Printf("filepath.Walk() returned %v
    ", err)
            return err
        }
        for _, v := range self.files {
            for _, file := range v {
                t, err := getTemplate(self.root, file, v...)
                if err != nil {
                    Trace("parse template err:", file, err)
                } else {
                    BeeTemplates[file] = t
                }
            }
        }
        return nil
    }
    

    比较复杂。一点点来看,os.Stat(dir)判断目录是否存在。filepath.Walk()走一边目录里的文件,记录在self.files里面。循环self.files中的file(map[dir][]file]),用getTemplate获取*template.Template实例,保存在beego.BeeTemplates(map[string]*template.Template)。

    为什么要预先编译模板?想像一下,如果每次请求,都去寻找模板再编译一遍。这显然是个浪费的。而且如果模板复杂,嵌套众多,编译速度会是很大的问题。因此存下编译好的*template.Template是必然的选择。但是,编译后模板的修改不能立即响应了,怎么办呢?先继续看下去。

    1.6 中间件

    middleware包目前似乎只有错误处理的功能。

    middleware.RegisterErrorHandler()
    

    只是注册默认的错误处理方法 middleware.NotFound 等几个。

    1.7 beeAdminApp

    if EnableAdmin {
        go beeAdminApp.Run()
    }
    

    beeAdminApp也是一个*beego.adminApp,负责系统监控、性能检测、访问统计和健康检查等。具体的介绍和使用可以访问文档

    2. HTTP服务

    写了这么多,终于要开始讲核心结构beego.BeeApp的启动:

    BeeApp.Run()
    

    Run()的实现代码在app.go#L29。代码较长,看看最重要的一段:

    if UseFcgi {
        if HttpPort == 0 {
            l, err = net.Listen("unix", addr)
        } else {
            l, err = net.Listen("tcp", addr)
        }
        if err != nil {
            BeeLogger.Critical("Listen: ", err)
        }
        err = fcgi.Serve(l, app.Handlers)
    } else {
        if EnableHotUpdate {
            server := &http.Server{
                Handler:      app.Handlers,
                ReadTimeout:  time.Duration(HttpServerTimeOut) * time.Second,
                WriteTimeout: time.Duration(HttpServerTimeOut) * time.Second,
            }
            laddr, err := net.ResolveTCPAddr("tcp", addr)
            if nil != err {
                BeeLogger.Critical("ResolveTCPAddr:", err)
            }
            l, err = GetInitListener(laddr)
            theStoppable = newStoppable(l)
            err = server.Serve(theStoppable)
            theStoppable.wg.Wait()
            CloseSelf()
        } else {
            s := &http.Server{
                Addr:         addr,
                Handler:      app.Handlers,
                ReadTimeout:  time.Duration(HttpServerTimeOut) * time.Second,
                WriteTimeout: time.Duration(HttpServerTimeOut) * time.Second,
            }
            if HttpTLS {
                err = s.ListenAndServeTLS(HttpCertFile, HttpKeyFile)
            } else {
                err = s.ListenAndServe()
            }
        }
    }
    

    beego.UseFcgi定义是否使用fast-cgi服务,而不是HTTP。另一部分是启动HTTP。里面有个重要功能EnableHotUpdate————热更新。对他的描述,可以看看官方文档

    2.1 HTTP过程总览

    上面的代码看得到*http.Server.Handlerapp.Handlers,即*beego.ControllerRegistorServeHTTP就定义在代码router.go#L431。非常长,我们检出重要的部分来说说。

    首先是要创建当前请求的上下文:

    // init context
    context := &beecontext.Context{
        ResponseWriter: w,
        Request:        r,
        Input:          beecontext.NewInput(r),
        Output:         beecontext.NewOutput(),
    }
    context.Output.Context = context
    context.Output.EnableGzip = EnableGzip
    

    context的类型是*context.Context,把当前的w(http.ResponseWriter)r(*http.Request)写在context的字段中。

    然后,定义了过滤器filter的调用方法,把context传递给过滤器操作:

    do_filter := func(pos int) (started bool) {
        if p.enableFilter {
            if l, ok := p.filters[pos]; ok {
                for _, filterR := range l {
                    if ok, p := filterR.ValidRouter(r.URL.Path); ok {
                        context.Input.Params = p
                        filterR.filterFunc(context)
                        if w.started {
                            return true
                        }
                    }
                }
            }
        }
        return false
    }
    

    然后,加载Session:

    if SessionOn {
        context.Input.CruSession = GlobalSessions.SessionStart(w, r)
        defer func() {
            context.Input.CruSession.SessionRelease(w)
        }()
    }
    

    defer中的SessionRelease()是将session持久化到存储引擎中,比如写入文件保存。

    然后,判断请求方式是否支持:

    if !utils.InSlice(strings.ToLower(r.Method), HTTPMETHOD) {
        http.Error(w, "Method Not Allowed", 405)
        goto Admin
    }
    

    这里看一看到 goto Admin,就是执行AdminApp的监控操作,记录这次请求的相关信息。Admin定义在整个HTTP执行的最后:

    Admin:
        //admin module record QPS
        if EnableAdmin {
            timeend := time.Since(starttime)
            if FilterMonitorFunc(r.Method, requestPath, timeend) {
                if runrouter != nil {
                    go toolbox.StatisticsMap.AddStatistics(r.Method, requestPath, runrouter.Name(), timeend)
                } else {
                    go toolbox.StatisticsMap.AddStatistics(r.Method, requestPath, "", timeend)
                }
            }
        }
    

    所以goto Admin直接就跳过中间过程,走到HTTP执行的最后了。显然,当请求方式不支持的时候,直接跳到HTTP执行最后。如果不启用AdminApp,那就是HTTP执行过程结束。

    继续阅读,开始处理静态文件了:

    if serverStaticRouter(context) {
        goto Admin
    }
    

    然后处理POST请求的内容体:

    if context.Input.IsPost() {
        if CopyRequestBody && !context.Input.IsUpload() {
            context.Input.CopyBody()
        }
        context.Input.ParseFormOrMulitForm(MaxMemory)
    }
    

    执行两个前置的过滤器:

    if do_filter(BeforeRouter) {
        goto Admin
    }
    
    if do_filter(AfterStatic) {
        goto Admin
    }
    

    不过我觉得这俩顺序怪怪的,应该先AfterStaticBeforeRouter。需要注意,过滤器如果返回false,整个执行就结束(跳到最后)。

    继续阅读,然后判断有没有指定执行的控制器和方法:

    if context.Input.RunController != nil && context.Input.RunMethod != "" {
        findrouter = true
        runMethod = context.Input.RunMethod
        runrouter = context.Input.RunController
    }
    

    如果过滤器执行后,对context指定了执行的控制器和方法,就用指定的。

    继续,路由的寻找开始,有三种路由:

    if !findrouter {
        for _, route := range p.fixrouters {
            n := len(requestPath)
            if requestPath == route.pattern {
                runMethod = p.getRunMethod(r.Method, context, route)
                if runMethod != "" {
                    runrouter = route.controllerType
                    findrouter = true
                    break
                }
            }
            //......
        }
    }
    

    p.fixrouters就是不带正则的路由,比如/userroute.controllerType的类型是reflect.Type,后面会用来创建控制器实例。p.getRunMethod()获取实际请求方式。为了满足浏览器无法发送表单PUTDELETE方法,可以用表单域_method值代替。(注明一下p就是*beego.ControllerRegistor

    接下来当然是正则的路由:

    if !findrouter {
        //find a matching Route
        for _, route := range p.routers {
    
            //check if Route pattern matches url
            if !route.regex.MatchString(requestPath) {
                continue
            }
            // ......
            runMethod = p.getRunMethod(r.Method, context, route)
            if runMethod != "" {
                runrouter = route.controllerType
                context.Input.Params = params
                findrouter = true
                break
            }
        }
    }
    

    正则路由比如/user/:id:int,这种带参数的。匹配后的参数会记录在context.Input.Params中。

    还没找到,就看看是否需要自动路由:

    if !findrouter && p.enableAuto {
        // ......
        for cName, methodmap := range p.autoRouter {
            // ......
        }
    }
    

    把所有路由规则走完,还是没有找到匹配的规则:

    if !findrouter {
        middleware.Exception("404", rw, r, "")
        goto Admin
    }
    

    另一种情况就是找到路由规则咯,且看下文。

    2.2 路由调用

    上面的代码发现路由的调用依赖runrouterrunmethod变量。他们值觉得了到底调用什么控制器和方法。来看看具体实现:

    if findrouter {
        //execute middleware filters
        if do_filter(BeforeExec) {
            goto Admin
        }
    
        //Invoke the request handler
        vc := reflect.New(runrouter)
        execController, ok := vc.Interface().(ControllerInterface)
        if !ok {
            panic("controller is not ControllerInterface")
        }
    
        //call the controller init function
        execController.Init(context, runrouter.Name(), runMethod, vc.Interface())
    
        //if XSRF is Enable then check cookie where there has any cookie in the  request's cookie _csrf
        if EnableXSRF {
            execController.XsrfToken()
            if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" ||
                (r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) {
                execController.CheckXsrfCookie()
            }
        }
    
        //call prepare function
        execController.Prepare()
    
        if !w.started {
            //exec main logic
            switch runMethod {
            case "Get":
                execController.Get()
            case "Post":
                execController.Post()
            case "Delete":
                execController.Delete()
            case "Put":
                execController.Put()
            case "Head":
                execController.Head()
            case "Patch":
                execController.Patch()
            case "Options":
                execController.Options()
            default:
                in := make([]reflect.Value, 0)
                method := vc.MethodByName(runMethod)
                method.Call(in)
            }
    
            //render template
            if !w.started && !context.Input.IsWebsocket() {
                if AutoRender {
                    if err := execController.Render(); err != nil {
                        panic(err)
                    }
    
                }
            }
        }
    
        // finish all runrouter. release resource
        execController.Finish()
    
        //execute middleware filters
        if do_filter(AfterExec) {
            goto Admin
        }
    }
    

    研读一下,最开始的又是过滤器:

    if do_filter(BeforeExec) {
        goto Admin
    }
    

    BeforeExec执行控制器方法前的过滤。

    然后,创建一个新的控制器实例:

    vc := reflect.New(runrouter)
    execController, ok := vc.Interface().(ControllerInterface)
    if !ok {
        panic("controller is not ControllerInterface")
    }
    
    //call the controller init function
    execController.Init(context, runrouter.Name(), runMethod, vc.Interface())
    

    reflect.New()创建新的实例,用vc.Interface().(ControllerInterface)取出,调用接口的Init方法,将请求的上下文等传递进去。 这里就说明为什么不能存下控制器实例给每次请求使用,因为每次请求的上下文是不同的

    execController.Prepare()
    

    控制器的准备工作,这里可以写用户登录验证等。

    然后根据runmethod执行控制器对应的方法,非接口定义的方法,用reflect.Call调用。

    if !w.started && !context.Input.IsWebsocket() {
        if AutoRender {
            if err := execController.Render(); err != nil {
                panic(err)
            }
        }
    }
    

    如果自动渲染AutoRender,就调用Render()方法渲染页面。

    execController.Finish()
    
    //execute middleware filters
    if do_filter(AfterExec) {
        goto Admin
    }
    

    控制器最后一刀Finish搞定,然后过滤器AfterExec使用。

    总结起来,beego.ControllerInterface接口方法的Init,Prepare,RenderFinish发挥很大作用。那就来研究一下。

    3. 控制器和视图

    3.1 控制器接口

    控制器接口beego.ControllerInterface的定义在controller.go#L47

    type ControllerInterface interface {
        Init(ct *context.Context, controllerName, actionName string, app interface{})
        Prepare()
        Get()
        Post()
        Delete()
        Put()
        Head()
        Patch()
        Options()
        Finish()
        Render() error
        XsrfToken() string
        CheckXsrfCookie() bool
    }
    

    官方的实现beego.Controller定义在controller.go#L29

    type Controller struct {
        Ctx            *context.Context
        Data           map[interface{}]interface{}
        controllerName string
        actionName     string
        TplNames       string
        Layout         string
        LayoutSections map[string]string // the key is the section name and the value is the template name
        TplExt         string
        _xsrf_token    string
        gotofunc       string
        CruSession     session.SessionStore
        XSRFExpire     int
        AppController  interface{}
        EnableReander  bool
    }
    

    内容好多,没必要全部都看看,重点在Init,Prepare,RenderFinish这四个。

    3.2 控制器的实现

    Init方法:

    // Init generates default values of controller operations.
    func (c *Controller) Init(ctx *context.Context, controllerName, actionName string, app interface{}) {
        c.Layout = ""
        c.TplNames = ""
        c.controllerName = controllerName
        c.actionName = actionName
        c.Ctx = ctx
        c.TplExt = "tpl"
        c.AppController = app
        c.EnableReander = true
        c.Data = ctx.Input.Data
    }
    

    没什么话说,一堆赋值。唯一要谈的是c.EnableReander,这种拼写错误实在是,掉阴沟里。实际的意思是EnableRender

    PrepareFinish方法:

    // Prepare runs after Init before request function execution.
    func (c *Controller) Prepare() {
    
    }
    
    // Finish runs after request function execution.
    func (c *Controller) Finish() {
    
    }
    

    空的!原来我要自己填内容啊。

    Render方法:

    // Render sends the response with rendered template bytes as text/html type.
    func (c *Controller) Render() error {
        if !c.EnableReander {
            return nil
        }
        rb, err := c.RenderBytes()
    
        if err != nil {
            return err
        } else {
            c.Ctx.Output.Header("Content-Type", "text/html; charset=utf-8")
            c.Ctx.Output.Body(rb)
        }
        return nil
    }
    

    3.3 视图渲染

    渲染的核心方法是c.RenderBytes():

    // RenderBytes returns the bytes of rendered template string. Do not send out response.
    func (c *Controller) RenderBytes() ([]byte, error) {
        //if the controller has set layout, then first get the tplname's content set the content to the layout
        if c.Layout != "" {
            if c.TplNames == "" {
                c.TplNames = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
            }
            if RunMode == "dev" {
                BuildTemplate(ViewsPath)
            }
            newbytes := bytes.NewBufferString("")
            if _, ok := BeeTemplates[c.TplNames]; !ok {
                panic("can't find templatefile in the path:" + c.TplNames)
                return []byte{}, errors.New("can't find templatefile in the path:" + c.TplNames)
            }
            err := BeeTemplates[c.TplNames].ExecuteTemplate(newbytes, c.TplNames, c.Data)
            if err != nil {
                Trace("template Execute err:", err)
                return nil, err
            }
            tplcontent, _ := ioutil.ReadAll(newbytes)
            c.Data["LayoutContent"] = template.HTML(string(tplcontent))
    
            if c.LayoutSections != nil {
                for sectionName, sectionTpl := range c.LayoutSections {
                    if sectionTpl == "" {
                        c.Data[sectionName] = ""
                        continue
                    }
    
                    sectionBytes := bytes.NewBufferString("")
                    err = BeeTemplates[sectionTpl].ExecuteTemplate(sectionBytes, sectionTpl, c.Data)
                    if err != nil {
                        Trace("template Execute err:", err)
                        return nil, err
                    }
                    sectionContent, _ := ioutil.ReadAll(sectionBytes)
                    c.Data[sectionName] = template.HTML(string(sectionContent))
                }
            }
    
            ibytes := bytes.NewBufferString("")
            err = BeeTemplates[c.Layout].ExecuteTemplate(ibytes, c.Layout, c.Data)
            if err != nil {
                Trace("template Execute err:", err)
                return nil, err
            }
            icontent, _ := ioutil.ReadAll(ibytes)
            return icontent, nil
        } else {
            //......
        }
        return []byte{}, nil
    }
    

    看起来很复杂,主要是两种情况,有没有Layout。如果有Layout:

    err := BeeTemplates[c.TplNames].ExecuteTemplate(newbytes, c.TplNames, c.Data)
    // ......
    tplcontent, _ := ioutil.ReadAll(newbytes)
    c.Data["LayoutContent"] = template.HTML(string(tplcontent))
    

    渲染模板文件,就是布局的主内容。

    for sectionName, sectionTpl := range c.LayoutSections {
        if sectionTpl == "" {
            c.Data[sectionName] = ""
            continue
        }
    
        sectionBytes := bytes.NewBufferString("")
        err = BeeTemplates[sectionTpl].ExecuteTemplate(sectionBytes, sectionTpl, c.Data)
        // ......
        sectionContent, _ := ioutil.ReadAll(sectionBytes)
        c.Data[sectionName] = template.HTML(string(sectionContent))
    }
    

    渲染布局里的别的区块c.LayoutSections

    ibytes := bytes.NewBufferString("")
    err = BeeTemplates[c.Layout].ExecuteTemplate(ibytes, c.Layout, c.Data)
    // ......
    icontent, _ := ioutil.ReadAll(ibytes)
    return icontent, nil
    

    最后是渲染布局文件,c.Data里带有所有布局的主内容和区块,可以直接赋值在布局里。

    渲染过程有趣的代码:

    if RunMode == "dev" {
        BuildTemplate(ViewsPath)
    }
    

    开发状态下,每次渲染都会重新BuildTemplate()。这样就可以理解,最初渲染模板并存下*template.Template,生产模式下,是不会响应即时的模版修改。

    总结

    本文对beego的执行过程进行了分析。一个Web应用,运行的过程就是路由分发,路由执行和结果渲染三个主要过程。本文没有非常详细的解释beego源码的细节分析,但是还是有几个重要问题进行的说明:

    • 路由规则的分类,固定的,还是正则,还是自动的。不同的路由处理方式不同,需要良好设计
    • 控制器的操作其实就是上下文的处理,使用控制器类,还是函数,需要根据应用考量。
    • 视图的效率控制需要严格把关,而且如何简单的设计就能满足复杂模板的使用,需要仔细考量。

    beego本身复杂,他的很多实现其实并不是很简洁直观。当然随着功能越来越强大,beego会越来越好的。

  • 相关阅读:
    django class类即视图类添加装饰器的几种方法
    django 如何在HMTL中使用媒体media_url
    django 对models中上传的文件或图片改名
    常用工具链接网址
    verifycode验证码模版
    shell 一些常用命令
    用 openresty 编写 lua
    centos7 安装python虚拟环境
    django url配置-反向解析-视图函数-HttpRequest对象-HttpResponse对象-cookies-session-redis缓存session
    PWN题搭建
  • 原文地址:https://www.cnblogs.com/rainbowzc/p/5635312.html
Copyright © 2011-2022 走看看