From 683d39a770098ae492acf5b3f322b11177680288 Mon Sep 17 00:00:00 2001 From: gzdaijie Date: Sun, 8 Sep 2019 22:04:11 +0800 Subject: [PATCH] add day6 doc & fix day6 day7 bug --- README.md | 2 +- day6-template/gee/gee.go | 2 +- day6-template/main.go | 2 +- day7-panic-recover/gee/gee.go | 2 +- doc/gee-day6.md | 190 ++++++++++++++++++++++++++++++++++ doc/gee-day6/html.png | Bin 0 -> 9294 bytes doc/gee.md | 2 +- 7 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 doc/gee-day6.md create mode 100644 doc/gee-day6/html.png diff --git a/README.md b/README.md index 120c319..5dee09d 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Gee 的设计与实现参考了Gin,这个教程可以快速入门:[Go Gin简 - [第三天:Tire树路由(Router)](https://geektutu.com/post/gee-day3.html),[Code - Github](day3-router) - [第四天:分组控制(Group)](https://geektutu.com/post/gee-day4.html),[Code - Github](day4-group) - [第五天:中间件(Middleware)](https://geektutu.com/post/gee-day5.html),[Code - Github](day5-middleware) -- 第六天:HTML模板(Template),[Code - Github](day6-template) +- [第六天:HTML模板(Template)](https://geektutu.com/post/gee-day6.html),[Code - Github](day6-template) - 第七天:错误恢复(Panic Recover),[Code - Github](day7-panic-recover) diff --git a/day6-template/gee/gee.go b/day6-template/gee/gee.go index e48a496..357f6e9 100644 --- a/day6-template/gee/gee.go +++ b/day6-template/gee/gee.go @@ -75,7 +75,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS absolutePath := path.Join(group.prefix, relativePath) fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { - file := c.Param("/filepath") + 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) diff --git a/day6-template/main.go b/day6-template/main.go index 92d4fe5..2400183 100644 --- a/day6-template/main.go +++ b/day6-template/main.go @@ -25,7 +25,7 @@ $ curl http://localhost:9999/students /* (3) serve static files -$ curl http://localhost:9999/assets/geektutu.css +$ curl http://localhost:9999/assets/css/geektutu.css p { color: orange; font-weight: 700; diff --git a/day7-panic-recover/gee/gee.go b/day7-panic-recover/gee/gee.go index 0643fb2..9eeb356 100644 --- a/day7-panic-recover/gee/gee.go +++ b/day7-panic-recover/gee/gee.go @@ -82,7 +82,7 @@ func (group *RouterGroup) createStaticHandler(relativePath string, fs http.FileS absolutePath := path.Join(group.prefix, relativePath) fileServer := http.StripPrefix(absolutePath, http.FileServer(fs)) return func(c *Context) { - file := c.Param("/filepath") + 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) diff --git a/doc/gee-day6.md b/doc/gee-day6.md new file mode 100644 index 0000000..ad4845b --- /dev/null +++ b/doc/gee-day6.md @@ -0,0 +1,190 @@ +--- +title: Go语言动手写Web框架 - Gee第六天 模板(HTML Template) +date: 2019-09-08 20:10:00 +description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。本文介绍了如何为Web框架添加HTML模板(HTML Template)以及静态文件(Serve Static Files)的功能。 +tags: +- Go +categories: +- 从零实现 +keywords: +- Go语言 +- 从零实现Web框架 +- 动手写Web框架 +- Template +image: post/gee-day6/html.png +github: https://github.com/geektutu/7days-golang +--- + +本文是 [7天用Go从零实现Web框架Gee教程系列](https://geektutu.com/post/gee.html)的第六篇。 + +- 实现静态资源服务(Static Resource)。 +- 支持HTML模板渲染。 + +## 服务端渲染 + +现在越来越流行前后端分离的开发模式,即 Web 后端提供 RESTful 接口,返回结构化的数据(通常为 JSON 或者 XML)。前端使用 AJAX 技术请求到所需的数据,利用 JavaScript 进行渲染。Vue/React 等前端框架持续火热,这种开发模式前后端解耦,优势非常突出。后端童鞋专心解决资源利用,并发,数据库等问题,只需要考虑数据如何生成;前端童鞋专注于界面设计实现,只需要考虑拿到数据后如何渲染即可。使用 JSP 写过网站的童鞋,应该能感受到前后端耦合的痛苦。JSP 的表现力肯定是远不如 Vue/React 等专业做前端渲染的框架的。而且前后端分离在当前还有另外一个不可忽视的优势。因为后端只关注于数据,接口返回值是结构化的,与前端解耦。同一套后端服务能够同时支撑小程序、移动APP、PC端 Web 页面,以及对外提供的接口。随着前端工程化的不断地发展,Webpack,gulp 等工具层出不穷,前端技术越来越自成体系了。 + +但前后分离的一大问题在于,页面是在客户端渲染的,比如浏览器,这对于爬虫并不友好。Google 爬虫已经能够爬取渲染后的网页,但是短期内爬取服务端直接渲染的 HTML 页面仍是主流。 + +今天的内容便是介绍 Web 框架如何支持服务端渲染的场景。 + +## 静态文件(Serve Static Files) + +网页的三剑客,JavaScript、CSS 和 HTML。要做到服务端渲染,第一步便是要支持 JS、CSS 等静态文件。还记得我们之前设计动态路由的时候,支持通配符`*`匹配多级子路径。比如路由规则`/assets/*filepath`,可以匹配`/assets/`开头的所有的地址。例如`/assets/js/geektutu.js`,匹配后,参数`filepath`就赋值为`js/geektutu.js`。 + +那如果我么将所有的静态文件放在`/usr/web`目录下,那么`filepath`的值即是该目录下文件的相对地址。映射到真实的文件后,将文件返回,静态服务器就实现了。 + +找到文件后,如何返回这一步,`net/http`库已经实现了。因此,gee 框架要做的,仅仅是解析请求的地址,映射到服务器上文件的真实地址,交给`http.FileServer`处理就好了。 + +[day6-template/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/day6-template/gee) + +```go +// 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`。例如: + +```go +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/template`2个模板标准库,其中[html/template](https://golang.org/pkg/html/template/)为 HTML 提供了较为完整的支持。包括普通变量渲染、列表渲染、对象渲染等。gee 框架的模板渲染直接使用了`html/template`提供的能力。 + +```go +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()`方法做了些小修改,使之支持根据模板文件名选择模板进行渲染。 + +[day6-template/gee/context.go](https://github.com/geektutu/7days-golang/tree/master/day6-template/gee) + +```go +func (c *Context) HTML(code int, name string, data interface{}) { + c.Writer.WriteHeader(code) + c.Writer.Header().Set("Content-Type", "text/html") + if err := c.engine.htmlTemplates.ExecuteTemplate(c.Writer, name, data); err != nil { + c.Fail(500, err.Error()) + } +} +``` + +## 使用Demo + +最终的目录结构 + +```bash +---gee/ +---static/ + |---css/ + |---geektutu.css + |---file1.txt +---templates/ + |---arr.tmpl + |---css.tmpl + |---custom_func.tmpl +---main.go +``` + +```html + + + +

geektutu.css is loaded

+ +``` + +[day6-template/main.go](https://github.com/geektutu/7days-golang/tree/master/day6-template/gee) + +```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 静态文件加载成功。 + +![day6-template](gee-day6/static.jpg) \ No newline at end of file diff --git a/doc/gee-day6/html.png b/doc/gee-day6/html.png new file mode 100644 index 0000000000000000000000000000000000000000..81cf086c9e0d91bcc3da2e83853ff100b275b33a GIT binary patch literal 9294 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 literal 0 HcmV?d00001 diff --git a/doc/gee.md b/doc/gee.md index 1fa945f..03f2c79 100644 --- a/doc/gee.md +++ b/doc/gee.md @@ -67,5 +67,5 @@ func handler(w http.ResponseWriter, r *http.Request) { - [第三天:Tire树路由(Router)](https://geektutu.com/post/gee-day3.html),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day3-router) - [第四天:分组控制(Group)](https://geektutu.com/post/gee-day4.html),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day4-group) - [第五天:中间件(Middleware)](https://geektutu.com/post/gee-day5.html),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day5-middleware) -- 第六天:HTML模板(Template),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day6-template) +- [第六天:HTML模板(Template)](https://geektutu.com/post/gee-day6.html),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day6-template) - 第七天:错误恢复(Panic Recover),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day7-panic-recover) \ No newline at end of file