当初风行的前后端拆散我的项目,应用更加业余的前端框架来制作页面,应用 ajax 进行数据交互,这样使得页面更加的业余。
然而前后端拆散的我的项目,还是很有毛病的,所以对于模版渲染这个性能还是要有的。
模版渲染的实现
网页的三剑客,JavaScript、CSS 和 HTML。要做到服务端渲染,第一步便是要反对 JS、CSS 等动态文件。还记得咱们之前设计动静路由的时候,反对通配符 匹配多级子门路。比方路由规定 /assets/filepath,能够匹配 /assets/ 结尾的所有的地址。例如 /assets/js/geektutu.js,匹配后,参数 filepath 就赋值为 js/geektutu.js。
那如果我么将所有的动态文件放在 /usr/web 目录下,那么 filepath 的值即是该目录下文件的绝对地址。映射到实在的文件后,将文件返回,动态服务器就实现了。
找到文件后,如何返回这一步,net/http 库曾经实现了。因而,gee 框架要做的,仅仅是解析申请的地址,映射到服务器上文件的实在地址,交给 http.FileServer 解决就好了。
// create static handler
func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileSystem) HandlerFunc {absolutePath := path.Join(group.prefix, relativePath)
fileServer := http.StripPrefix(absolutePath, http.FileServer(fs))
return func(c *Context) {file := c.Param("filepath")
// Check if file exists and/or if we have permission to access it
if _, err := fs.Open(file); err != nil {c.Status(http.StatusNotFound)
return
}
fileServer.ServeHTTP(c.Writer, c.Req)
}
}
// serve static files
func (group *RouterGroup) Static(relativePath string, root string) {handler := group.createStaticHandler(relativePath, http.Dir(root))
urlPattern := path.Join(relativePath, "/*filepath")
// Register GET handlers
group.GET(urlPattern, handler)
}
咱们给 RouterGroup 增加了 2 个办法,Static 这个办法是裸露给用户的。用户能够将磁盘上的某个文件夹 root 映射到路由 relativePath。例如:
r := gee.New()
r.Static("/assets", "/usr/geektutu/blog/static")
// 或相对路径 r.Static("/assets", "./static")
r.Run(":9999")
用户拜访 localhost:9999/assets/js/geektutu.js,最终返回 /usr/geektutu/blog/static/js/geektutu.js。
HTML 模板渲染
Go 语言内置了 text/template 和 html/template2 个模板规范库,其中 html/template 为 HTML 提供了较为残缺的反对。包含一般变量渲染、列表渲染、对象渲染等。gee 框架的模板渲染间接应用了 html/template 提供的能力。
Engine struct {
*RouterGroup
router *router
groups []*RouterGroup // store all groups
htmlTemplates *template.Template // for html render
funcMap template.FuncMap // for html render
}
func (engine *Engine) SetFuncMap(funcMap template.FuncMap) {engine.funcMap = funcMap}
func (engine *Engine) LoadHTMLGlob(pattern string) {engine.htmlTemplates = template.Must(template.New("").Funcs(engine.funcMap).ParseGlob(pattern))
}
首先为 Engine 示例增加了 *template.Template 和 template.FuncMap 对象,前者将所有的模板加载进内存,后者是所有的自定义模板渲染函数。
另外,给用户别离提供了设置自定义渲染函数 funcMap 和加载模板的办法。
接下来,对原来的 (*Context).HTML()办法做了些小批改,使之反对依据模板文件名抉择模板进行渲染。
type Context struct {
// ...
// engine pointer
engine *Engine
}
func (c *Context) HTML(code int, name string, data interface{}) {c.SetHeader("Content-Type", "text/html")
c.Status(code)
if err := c.engine.htmlTemplates.ExecuteTemplate(c.Writer, name, data); err != nil {c.Fail(500, err.Error())
}
}
咱们在 Context 中增加了成员变量 engine *Engine,这样就可能通过 Context 拜访 Engine 中的 HTML 模板。实例化 Context 时,还须要给 c.engine 赋值。
func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) {
// ...
c := newContext(w, req)
c.handlers = middlewares
c.engine = engine
engine.router.handle(c)
}
应用 Demo
最终的目录构造
—gee/
—static/
|—css/
|---geektutu.css
|—file1.txt
—templates/
|—arr.tmpl
|—css.tmpl
|—custom_func.tmpl
—main.go
<!-- day6-template/templates/css.tmpl -->
<html>
<link rel="stylesheet" href="/assets/css/geektutu.css">
<p>geektutu.css is loaded</p>
</html>
day6-template/main.go
type student struct {
Name string
Age int8
}
func FormatAsDate(t time.Time) string {year, month, day := t.Date()
return fmt.Sprintf("%d-%02d-%02d", year, month, day)
}
func main() {r := gee.New()
r.Use(gee.Logger())
r.SetFuncMap(template.FuncMap{"FormatAsDate": FormatAsDate,})
r.LoadHTMLGlob("templates/*")
r.Static("/assets", "./static")
stu1 := &student{Name: "Geektutu", Age: 20}
stu2 := &student{Name: "Jack", Age: 22}
r.GET("/", func(c *gee.Context) {c.HTML(http.StatusOK, "css.tmpl", nil)
})
r.GET("/students", func(c *gee.Context) {
c.HTML(http.StatusOK, "arr.tmpl", gee.H{
"title": "gee",
"stuArr": [2]*student{stu1, stu2},
})
})
r.GET("/date", func(c *gee.Context) {
c.HTML(http.StatusOK, "custom_func.tmpl", gee.H{
"title": "gee",
"now": time.Date(2019, 8, 17, 0, 0, 0, 0, time.UTC),
})
})
r.Run(":9999")
}
拜访下主页,模板失常渲染,CSS 动态文件加载胜利。