Beego源码分析

前端之家收集整理的这篇文章主要介绍了Beego源码分析前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

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

注意,本文基于beego 1.1.4 (2014.04.15) 源码分析,且不是beego的使用教程。使用细节的问题在这里不会说明。

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

1. 启动应用

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

  1. package main
  2.  
  3. import "github.com/astaxie/beego"
  4.  
  5. func main() {
  6. beego.Run()
  7. }

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

  1. func Run() {
  2. initBeforeHttpRun()
  3.  
  4. if EnableAdmin {
  5. go beeAdminApp.Run()
  6. }
  7.  
  8. BeeApp.Run()
  9. }

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

  1. func initBeforeHttpRun() {
  2. // if AppConfigPath not In the conf/app.conf reParse config
  3. if AppConfigPath != filepath.Join(AppPath,"conf","app.conf") {
  4. err := ParseConfig()
  5. if err != nil && AppConfigPath != filepath.Join(workPath,"app.conf") {
  6. // configuration is critical to app,panic here if parse Failed
  7. panic(err)
  8. }
  9. }
  10.  
  11. // do hooks function
  12. for _,hk := range hooks {
  13. err := hk()
  14. if err != nil {
  15. panic(err)
  16. }
  17. }
  18.  
  19. if SessionOn {
  20. var err error
  21. sessionConfig := AppConfig.String("sessionConfig")
  22. if sessionConfig == "" {
  23. sessionConfig = `{"cookieName":"` + SessionName + `",` +
  24. `"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime,10) + `,` +
  25. `"providerConfig":"` + SessionSavePath + `",` +
  26. `"secure":` + strconv.FormatBool(HttpTLS) + `,` +
  27. `"sessionIDHashFunc":"` + SessionHashFunc + `",` +
  28. `"sessionIDHashKey":"` + SessionHashKey + `",` +
  29. `"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` +
  30. `"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}`
  31. }
  32. GlobalSessions,err = session.NewManager(SessionProvider,sessionConfig)
  33. if err != nil {
  34. panic(err)
  35. }
  36. go GlobalSessions.GC()
  37. }
  38.  
  39. err := BuildTemplate(ViewsPath)
  40. if err != nil {
  41. if RunMode == "dev" {
  42. Warn(err)
  43. }
  44. }
  45.  
  46. middleware.VERSION = VERSION
  47. middleware.AppName = AppName
  48. middleware.RegisterErrorHandler()
  49. }

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

1.1 加载配置@H_404_56@

加载配置的代码是:

  1. if AppConfigPath != filepath.Join(AppPath,"app.conf") {
  2. err := ParseConfig()
  3. if err != nil && AppConfigPath != filepath.Join(workPath,"app.conf") {
  4. // configuration is critical to app,panic here if parse Failed
  5. panic(err)
  6. }
  7. }

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

  1. workPath,_ = os.Getwd()
  2. workPath,_ = filepath.Abs(workPath)
  3. // initialize default configurations
  4. AppPath,_ = filepath.Abs(filepath.Dir(os.Args[0]))
  5.  
  6. AppConfigPath = filepath.Join(AppPath,"app.conf")
  7.  
  8. if workPath != AppPath {
  9. if utils.FileExists(AppConfigPath) {
  10. os.Chdir(AppPath)
  11. } else {
  12. AppConfigPath = filepath.Join(workPath,"app.conf")
  13. }
  14. }

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

1.2 Hooks@H_404_56@

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

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

hooks的定义在beego.go#L19

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

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

1.3 单实例@H_404_56@

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@H_404_56@

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

  1. if SessionOn {
  2. var err error
  3. sessionConfig := AppConfig.String("sessionConfig")
  4. if sessionConfig == "" {
  5. sessionConfig = `{"cookieName":"` + SessionName + `",` +
  6. `"gclifetime":` + strconv.FormatInt(SessionGCMaxLifetime,` +
  7. `"providerConfig":"` + SessionSavePath + `",` +
  8. `"secure":` + strconv.FormatBool(HttpTLS) + `,` +
  9. `"sessionIDHashFunc":"` + SessionHashFunc + `",` +
  10. `"sessionIDHashKey":"` + SessionHashKey + `",` +
  11. `"enableSetCookie":` + strconv.FormatBool(SessionAutoSetCookie) + `,` +
  12. `"cookieLifeTime":` + strconv.Itoa(SessionCookieLifeTime) + `}`
  13. }
  14. GlobalSessions,sessionConfig)
  15. if err != nil {
  16. panic(err)
  17. }
  18. go GlobalSessions.GC()
  19. }

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

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

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

1.5 模板构建@H_404_56@

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

  1. err := BuildTemplate(ViewsPath)

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

  1. // build all template files in a directory.
  2. // it makes beego can render any template file in view directory.
  3. func BuildTemplate(dir string) error {
  4. if _,err := os.Stat(dir); err != nil {
  5. if os.IsNotExist(err) {
  6. return nil
  7. } else {
  8. return errors.New("dir open err")
  9. }
  10. }
  11. self := &templatefile{
  12. root: dir,files: make(map[string][]string),}
  13. err := filepath.Walk(dir,func(path string,f os.FileInfo,err error) error {
  14. return self.visit(path,f,err)
  15. })
  16. if err != nil {
  17. fmt.Printf("filepath.Walk() returned %v\n",err)
  18. return err
  19. }
  20. for _,v := range self.files {
  21. for _,file := range v {
  22. t,err := getTemplate(self.root,file,v...)
  23. if err != nil {
  24. Trace("parse template err:",err)
  25. } else {
  26. BeeTemplates[file] = t
  27. }
  28. }
  29. }
  30. return nil
  31. }

比较复杂。一点点来看,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 中间件@H_404_56@

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

  1. middleware.RegisterErrorHandler()

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

1.7 beeAdminApp@H_404_56@
  1. if EnableAdmin {
  2. go beeAdminApp.Run()
  3. }

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

2. HTTP服务

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

  1. BeeApp.Run()

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

  1. if UseFcgi {
  2. if HttpPort == 0 {
  3. l,err = net.Listen("unix",addr)
  4. } else {
  5. l,err = net.Listen("tcp",addr)
  6. }
  7. if err != nil {
  8. BeeLogger.Critical("Listen: ",err)
  9. }
  10. err = fcgi.Serve(l,app.Handlers)
  11. } else {
  12. if EnableHotUpdate {
  13. server := &http.Server{
  14. Handler: app.Handlers,ReadTimeout: time.Duration(HttpServerTimeOut) * time.Second,WriteTimeout: time.Duration(HttpServerTimeOut) * time.Second,}
  15. laddr,err := net.ResolveTCPAddr("tcp",addr)
  16. if nil != err {
  17. BeeLogger.Critical("ResolveTCPAddr:",err)
  18. }
  19. l,err = GetInitListener(laddr)
  20. theStoppable = newStoppable(l)
  21. err = server.Serve(theStoppable)
  22. theStoppable.wg.Wait()
  23. CloseSelf()
  24. } else {
  25. s := &http.Server{
  26. Addr: addr,Handler: app.Handlers,}
  27. if HttpTLS {
  28. err = s.ListenAndServeTLS(HttpCertFile,HttpKeyFile)
  29. } else {
  30. err = s.ListenAndServe()
  31. }
  32. }
  33. }

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

2.1 HTTP过程总览@H_404_56@

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

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

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

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

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

  1. do_filter := func(pos int) (started bool) {
  2. if p.enableFilter {
  3. if l,ok := p.filters[pos]; ok {
  4. for _,filterR := range l {
  5. if ok,p := filterR.ValidRouter(r.URL.Path); ok {
  6. context.Input.Params = p
  7. filterR.filterFunc(context)
  8. if w.started {
  9. return true
  10. }
  11. }
  12. }
  13. }
  14. }
  15. return false
  16. }

然后,加载Session:

  1. if SessionOn {
  2. context.Input.CruSession = GlobalSessions.SessionStart(w,r)
  3. defer func() {
  4. context.Input.CruSession.SessionRelease(w)
  5. }()
  6. }

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

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

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

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

  1. Admin:
  2. //admin module record QPS
  3. if EnableAdmin {
  4. timeend := time.Since(starttime)
  5. if FilterMonitorFunc(r.Method,requestPath,timeend) {
  6. if runrouter != nil {
  7. go toolBox.StatisticsMap.AddStatistics(r.Method,runrouter.Name(),timeend)
  8. } else {
  9. go toolBox.StatisticsMap.AddStatistics(r.Method,"",timeend)
  10. }
  11. }
  12. }

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

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

  1. if serverStaticRouter(context) {
  2. goto Admin
  3. }

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

  1. if context.Input.IsPost() {
  2. if CopyRequestBody && !context.Input.IsUpload() {
  3. context.Input.CopyBody()
  4. }
  5. context.Input.ParseFormOrMulitForm(MaxMemory)
  6. }

执行两个前置的过滤器:

  1. if do_filter(BeforeRouter) {
  2. goto Admin
  3. }
  4.  
  5. if do_filter(AfterStatic) {
  6. goto Admin
  7. }

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

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

  1. if context.Input.RunController != nil && context.Input.RunMethod != "" {
  2. findrouter = true
  3. runMethod = context.Input.RunMethod
  4. runrouter = context.Input.RunController
  5. }

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

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

  1. if !findrouter {
  2. for _,route := range p.fixrouters {
  3. n := len(requestPath)
  4. if requestPath == route.pattern {
  5. runMethod = p.getRunMethod(r.Method,context,route)
  6. if runMethod != "" {
  7. runrouter = route.controllerType
  8. findrouter = true
  9. break
  10. }
  11. }
  12. //......
  13. }
  14. }

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

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

  1. if !findrouter {
  2. //find a matching Route
  3. for _,route := range p.routers {
  4.  
  5. //check if Route pattern matches url
  6. if !route.regex.MatchString(requestPath) {
  7. continue
  8. }
  9. // ......
  10. runMethod = p.getRunMethod(r.Method,route)
  11. if runMethod != "" {
  12. runrouter = route.controllerType
  13. context.Input.Params = params
  14. findrouter = true
  15. break
  16. }
  17. }
  18. }

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

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

  1. if !findrouter && p.enableAuto {
  2. // ......
  3. for cName,methodmap := range p.autoRouter {
  4. // ......
  5. }
  6. }

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

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

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

2.2 路由调用@H_404_56@

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

  1. if findrouter {
  2. //execute middleware filters
  3. if do_filter(BeforeExec) {
  4. goto Admin
  5. }
  6.  
  7. //Invoke the request handler
  8. vc := reflect.New(runrouter)
  9. execController,ok := vc.Interface().(ControllerInterface)
  10. if !ok {
  11. panic("controller is not ControllerInterface")
  12. }
  13.  
  14. //call the controller init function
  15. execController.Init(context,runMethod,vc.Interface())
  16.  
  17. //if XSRF is Enable then check cookie where there has any cookie in the request's cookie _csrf
  18. if EnableXSRF {
  19. execController.XsrfToken()
  20. if r.Method == "POST" || r.Method == "DELETE" || r.Method == "PUT" ||
  21. (r.Method == "POST" && (r.Form.Get("_method") == "delete" || r.Form.Get("_method") == "put")) {
  22. execController.CheckXsrfCookie()
  23. }
  24. }
  25.  
  26. //call prepare function
  27. execController.Prepare()
  28.  
  29. if !w.started {
  30. //exec main logic
  31. switch runMethod {
  32. case "Get":
  33. execController.Get()
  34. case "Post":
  35. execController.Post()
  36. case "Delete":
  37. execController.Delete()
  38. case "Put":
  39. execController.Put()
  40. case "Head":
  41. execController.Head()
  42. case "Patch":
  43. execController.Patch()
  44. case "Options":
  45. execController.Options()
  46. default:
  47. in := make([]reflect.Value,0)
  48. method := vc.MethodByName(runMethod)
  49. method.Call(in)
  50. }
  51.  
  52. //render template
  53. if !w.started && !context.Input.IsWebsocket() {
  54. if AutoRender {
  55. if err := execController.Render(); err != nil {
  56. panic(err)
  57. }
  58.  
  59. }
  60. }
  61. }
  62.  
  63. // finish all runrouter. release resource
  64. execController.Finish()
  65.  
  66. //execute middleware filters
  67. if do_filter(AfterExec) {
  68. goto Admin
  69. }
  70. }

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

  1. if do_filter(BeforeExec) {
  2. goto Admin
  3. }

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

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

  1. vc := reflect.New(runrouter)
  2. execController,ok := vc.Interface().(ControllerInterface)
  3. if !ok {
  4. panic("controller is not ControllerInterface")
  5. }
  6.  
  7. //call the controller init function
  8. execController.Init(context,vc.Interface())

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

  1. execController.Prepare()

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

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

  1. if !w.started && !context.Input.IsWebsocket() {
  2. if AutoRender {
  3. if err := execController.Render(); err != nil {
  4. panic(err)
  5. }
  6. }
  7. }

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

  1. execController.Finish()
  2.  
  3. //execute middleware filters
  4. if do_filter(AfterExec) {
  5. goto Admin
  6. }

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

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

3. 控制器和视图

3.1 控制器接口@H_404_56@

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

  1. type ControllerInterface interface {
  2. Init(ct *context.Context,controllerName,actionName string,app interface{})
  3. Prepare()
  4. Get()
  5. Post()
  6. Delete()
  7. Put()
  8. Head()
  9. Patch()
  10. Options()
  11. Finish()
  12. Render() error
  13. XsrfToken() string
  14. CheckXsrfCookie() bool
  15. }

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

  1. type Controller struct {
  2. Ctx *context.Context
  3. Data map[interface{}]interface{}
  4. controllerName string
  5. actionName string
  6. TplNames string
  7. Layout string
  8. LayoutSections map[string]string // the key is the section name and the value is the template name
  9. TplExt string
  10. _xsrf_token string
  11. gotofunc string
  12. CruSession session.SessionStore
  13. XSRFExpire int
  14. AppController interface{}
  15. EnableReander bool
  16. }

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

3.2 控制器的实现@H_404_56@

Init方法

  1. // Init generates default values of controller operations.
  2. func (c *Controller) Init(ctx *context.Context,app interface{}) {
  3. c.Layout = ""
  4. c.TplNames = ""
  5. c.controllerName = controllerName
  6. c.actionName = actionName
  7. c.Ctx = ctx
  8. c.TplExt = "tpl"
  9. c.AppController = app
  10. c.EnableReander = true
  11. c.Data = ctx.Input.Data
  12. }

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

PrepareFinish方法

  1. // Prepare runs after Init before request function execution.
  2. func (c *Controller) Prepare() {
  3.  
  4. }
  5.  
  6. // Finish runs after request function execution.
  7. func (c *Controller) Finish() {
  8.  
  9. }

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

Render方法

  1. // Render sends the response with rendered template bytes as text/html type.
  2. func (c *Controller) Render() error {
  3. if !c.EnableReander {
  4. return nil
  5. }
  6. rb,err := c.RenderBytes()
  7.  
  8. if err != nil {
  9. return err
  10. } else {
  11. c.Ctx.Output.Header("Content-Type","text/html; charset=utf-8")
  12. c.Ctx.Output.Body(rb)
  13. }
  14. return nil
  15. }

3.3 视图渲染@H_404_56@

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

  1. // RenderBytes returns the bytes of rendered template string. Do not send out response.
  2. func (c *Controller) RenderBytes() ([]byte,error) {
  3. //if the controller has set layout,then first get the tplname's content set the content to the layout
  4. if c.Layout != "" {
  5. if c.TplNames == "" {
  6. c.TplNames = strings.ToLower(c.controllerName) + "/" + strings.ToLower(c.actionName) + "." + c.TplExt
  7. }
  8. if RunMode == "dev" {
  9. BuildTemplate(ViewsPath)
  10. }
  11. newbytes := bytes.NewBufferString("")
  12. if _,ok := BeeTemplates[c.TplNames]; !ok {
  13. panic("can't find templatefile in the path:" + c.TplNames)
  14. return []byte{},errors.New("can't find templatefile in the path:" + c.TplNames)
  15. }
  16. err := BeeTemplates[c.TplNames].ExecuteTemplate(newbytes,c.TplNames,c.Data)
  17. if err != nil {
  18. Trace("template Execute err:",err)
  19. return nil,err
  20. }
  21. tplcontent,_ := IoUtil.ReadAll(newbytes)
  22. c.Data["LayoutContent"] = template.HTML(string(tplcontent))
  23.  
  24. if c.LayoutSections != nil {
  25. for sectionName,sectionTpl := range c.LayoutSections {
  26. if sectionTpl == "" {
  27. c.Data[sectionName] = ""
  28. continue
  29. }
  30.  
  31. sectionBytes := bytes.NewBufferString("")
  32. err = BeeTemplates[sectionTpl].ExecuteTemplate(sectionBytes,sectionTpl,c.Data)
  33. if err != nil {
  34. Trace("template Execute err:",err)
  35. return nil,err
  36. }
  37. sectionContent,_ := IoUtil.ReadAll(sectionBytes)
  38. c.Data[sectionName] = template.HTML(string(sectionContent))
  39. }
  40. }
  41.  
  42. ibytes := bytes.NewBufferString("")
  43. err = BeeTemplates[c.Layout].ExecuteTemplate(ibytes,c.Layout,err
  44. }
  45. icontent,_ := IoUtil.ReadAll(ibytes)
  46. return icontent,nil
  47. } else {
  48. //......
  49. }
  50. return []byte{},nil
  51. }

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

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

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

  1. for sectionName,sectionTpl := range c.LayoutSections {
  2. if sectionTpl == "" {
  3. c.Data[sectionName] = ""
  4. continue
  5. }
  6.  
  7. sectionBytes := bytes.NewBufferString("")
  8. err = BeeTemplates[sectionTpl].ExecuteTemplate(sectionBytes,c.Data)
  9. // ......
  10. sectionContent,_ := IoUtil.ReadAll(sectionBytes)
  11. c.Data[sectionName] = template.HTML(string(sectionContent))
  12. }

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

  1. ibytes := bytes.NewBufferString("")
  2. err = BeeTemplates[c.Layout].ExecuteTemplate(ibytes,c.Data)
  3. // ......
  4. icontent,_ := IoUtil.ReadAll(ibytes)
  5. return icontent,nil

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

渲染过程有趣的代码

  1. if RunMode == "dev" {
  2. BuildTemplate(ViewsPath)
  3. }

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

总结

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

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

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

猜你在找的Go相关文章