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時進行讀取,形成耗時太長。
後面的內容會講述如何將模板預編譯放到服務啓動時進行,同時說明怎樣支持模板變動後的動態熱更新。