WEB应用的处理流程中,获取请求参数,调用业务逻辑处理后,下一步就是响应输出,在GO中,就是向ResponseWriter写数据。html
fmt.Fprintf(this.ResponseWriter, format, data...)
对于常规输出,作一个简单的封装,以简化调用的方式nginx
func (this *App)Resp(format string,data ...interface{}){ fmt.Fprintf(this.ResponseWriter, format, data...) } // 调用 // this.Resp("hello %s",User.Name)
http协议中,响应内容除了body的输出,还包括 Header和Cookie等,对这两种也进行一个简单的封装数据库
func (this *App) SetHeader(key, val string){ } func (this *App) SetCookie(c ...interface{}) (err error){ // 支持三种方式(判断参数c的个数和类型) // SetCookie(name string,val string) // SetCookie(name string,val string,expire int) // SetCookie(c *http.Cookie) }
http.ResponseWriter只是一个简单的接口,若是想要记录所响应的状态码、响应大小(access_log中用到),就须要扩展该接口。this
type resWriter struct { http.ResponseWriter Length int Code int } //重写Write方法,记录响应的内容大小 func (this *resWriter) Write(b []byte) (n int, err error) { n, err = this.ResponseWriter.Write(b) this.Length += n return } //重写WriterHeader方法,记录响应码 func (this *resWriter) WriteHeader(code int) { this.ResponseWriter.WriteHeader(code) this.Code = code }
这样,在须要用到ResponseWriter做为参数的地方,统一使用resWriter替换,resWriter除了调用ResponseWriter来正常输出,还会自动记录响应内容的大小和状态码了。code
** html/template ** 包提供了使用视图模板进行渲染的功能。其基本使用方法是:regexp
tpl := "" //模板内容 tmpl, err := template.New("test").Parse(tpl) //check err tmpl.Execute(this.ResWriter, data)
s1, _ := template.ParseFiles("a.tmpl") s1.Exeute(this.ResWriter,data)
实际应用时,通常会将一个网页分红头部,内容和页脚等多个子模板,传统的方式这样编写模板:orm
//header.tpl {{define "header"}} <!doctype html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title></title> </head> <body> {{end}} //body.tpl {{define "content"}} {{template "header"}} <h1>演示嵌套</h1> <ul> <li>嵌套使用define定义子模板</li> <li>调用使用template</li> </ul> {{template "footer"}} {{end}} //footer.tpl {{define "footer"}} </body> </html> {{end}}
而后用如下方式渲染:htm
t, _ := template.ParseFiles("header.tpl", "body.tpl", "footer.tpl") t.ExecuteTemplate(this.ResWriter, "header", nil) t.ExecuteTemplate(this.ResWriter, "body", nil) t.ExecuteTemplate(this.ResWriter, "footer", nil) t.Execute(this.ResWriter, nil)
能够看出,这种方式不管是编写模板仍是渲染都有不足:递归
模板的定义有点罗嗦,子模板要使用define定义,引用子模板的地方还要用template关键字声明接口
渲染时要屡次调用Excute
我理想的多模板组织方式是:
//main.tpl {{#include header.tpl}} <body> <h1>演示嵌套</h1> <ul> <li>嵌套使用define定义子模板</li> <li>调用使用template</li> </ul> </body> {{#include footer.tpl}} //header.tpl <!doctype html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <title></title> </head> //footer.tpl </body> </html>
这种方式与人的思惟比较接近,比较容易理解。
更好的方式是:能够改用nginx的SSI语法来include,能够直接进行全局预览:
<!--# include file="footer.tpl" -->
而后,直接在Controller中使用** this.Render("main.tpl",data) ** 调用便可,内部的引用自动完成。
使用者无需知道主模板使用了多个子模板,更重要的是,修改了模板结构后(好比再分拆出一个子模板),只要里面的要替换的变量不变,根本不用改代码和重编译,甚至是服务都不用重启。
下面来看看支持上述方式使用模板的Render如何实现:
基本思路是读取模板文件内容,用正则搜索其中的 {{#include xxx}}内容,获得全部子模板,并将子模板的内容读取回填到相应位置(暂不支持子模板再include子模板,要支持其实也是一个递归,但不必)
func (this *App) Render(tpl string, data interface{}) viewPath : = "" //模板文件目录 mf, err := os.Open(ViewPath + tpl) //check err defer mf.Close() content, _ := ioutil.ReadAll(mf) reg := regexp.MustCompile(`\{\{#include "(.*)"\}\}`) //遍历引用的子文件 for _, v := range reg.FindAllSubmatch(content, -1) { //遍历匹配到的内容进行替换 incFile := fmt.Sprintf("%s/%s", viewPath, v[1]) //同一层目录 f1, err := os.Open(incFile) //check error defer f1.Close() incContent, _ := ioutil.ReadAll(f1) content = bytes.Replace(content, v[0], incContent, 1) } t := template.New(tpl).Parse(string(content)) t.Excute(this.ResWriter,data) }
需注意的是,模板的编译最好在服务启动时就进行,避免在Render时进行读取,形成耗时太长。
后面的内容会讲述如何将模板预编译放到服务启动时进行,同时说明怎样支持模板变动后的动态热更新。