day6-模板 Template
Q7nl1s admin

这是用 go 写 Web 框架 Gee 的第六天。

本节主要实现:

  • 实现静态资源服务(Static Resource)。
  • 支持HTML模板渲染。

在这之前我们先来了解两个概念:

  1. RESTful 风格
  2. AJAX

RESTful 风格:RESTFUL 是一种网络应用程序的设计风格和开发方式,基于 HTTP可以使用 XML 格式定义或 JSON 格式定义。最常用的数据格式是 JSON 。由于 JSON 能直接被 JavaScript 读取,所以,使用 JSON 格式的 REST 风格的 API 具有简单、易读、易用的特点

AJAX:

  • ajax 全名 async javascript and XML(异步 JavaScript 和 XML)
  • 前后台交互的能⼒ 也就是我们客户端给服务端发送消息的⼯具,以及接受响应的⼯具
  • AJAX 不是新的编程语言,而是一种使用现有标准的新方法。
  • AJAX 是与服务器交换数据并更新部分网页的艺术,在不重新加载整个页面的情况下。
  • 是⼀个 默认异步 执⾏机制的功能,AJAX 分为同步(async = false)和异步(async = true)

同步请求:同步请求是指当前发出请求后,浏览器什么都不能做,必须得等到请求完成返回数据之后,才会执行后续的代码,相当于生活中的排队,必须等待前一个人完成自己的事物,后一个人才能接着办。也就是说,当 JS 代码加载到当前 AJAX 的时候会把页面里所有的代码停止加载,页面处于一个假死状态,当这个 AJAX 执行完毕后才会继续运行其他代码页面解除假死状态

异步请求:异步请求就当发出请求的同时,浏览器可以继续做任何事,Ajax 发送请求并不会影响页面的加载与用户的操作,相当于是在两条线上,各走各的,互不影响。一般默认值为 true,异步。异步请求可以完全不影响用户的体验效果,无论请求的时间长或者短,用户都在专心的操作页面的其他内容,并不会有等待的感觉。

AJAX 的优势:

  • 不需要插件的⽀持,原⽣ js 就可以使⽤
  • ⽤户体验好(不需要刷新⻚⾯就可以更新数据
  • 减轻服务端和带宽的负担
  • 缺点:搜索引擎的⽀持度不够,因为数据都不在⻚⾯上,搜索引擎搜索不到

服务端渲染

现在越来越流行前后端分离的开发模式,即 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 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 。例如:

1
2
3
4
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/templatehtml/template 2个模板标准库,其中 html/template 为 HTML 提供了较为完整的支持。包括普通变量渲染、列表渲染、对象渲染等。gee 框架的模板渲染直接使用了 html/template 提供的能力。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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.Templatetemplate.FuncMap对象,前者将所有的模板加载进内存,后者是所有的自定义模板渲染函数。

另外,给用户分别提供了设置自定义渲染函数 funcMap 和加载模板的方法。

接下来,对原来的 (*Context).HTML() 方法做了些小修改,使之支持根据模板文件名选择模板进行渲染。

day6-template/gee/context.go

1
2
3
4
5
6
7
8
9
10
11
12
13
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 赋值。

day6-template/gee/gee.go

1
2
3
4
5
6
7
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

最终的目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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 静态文件加载成功。

参考:

7days-golang/gee-web/day6-template at master · geektutu/7days-golang (github.com)

看这一篇就够了!-Ajax详解_抱着猫睡觉的鱼_的博客-CSDN博客

RESTful 风格(详细介绍 + 案例实现)_Yan Yang的博客-CSDN博客_restful风格

 Comments
Comment plugin failed to load
Loading comment plugin
Powered by Hexo & Theme Keep
Unique Visitor Page View