Golang实践录:静态资源文件整合:web服务

趁着五一放假,趁着有时间,把欠的一些技术集中研究研究,写写文章,好给自己一个交待。
本文研究静态资源文件的在 web 服务器的整合。

基础

Golang 中的 web 服务框架有很多种,本文选取 gin 实现。gin 实现一个 web 服务仅需几行代码,十分方便。但为了适应更复杂的项目,还需要进行一些改进。
web 服务页面的文件,除了 html 外,还有 css、js、图片等文件,为方便管理,将后者放到 static 目录——与前面文章目录保持一致,将前者放到 templates 目录,使用 gin 的 html 模板进行渲染,而且 gin 也支持自定义模板文件,恰好能适合我们的场景。

实践

资源文件

本文使用到的资源文件如下:

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
$ tree static/
static/
|-- css
| |-- bootstrap.min.css
| |-- font-awesome.min.css
| `-- main.css
|-- favicon.ico
`-- js
|-- bootstrap.min.js
|-- jquery-1.8.3.min.js
|-- jquery-2.0.0.min.js
`-- main.js

2 directories, 8 files

$ tree templates/
templates/
|-- about.html
|-- about.js
|-- index.html
|-- login.html
|-- login.js
`-- nav.js

0 directories, 6 files

通用方式

为方便对比,先给出通用的 gin 框架,主要代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

// 直接引用文件形式
func webServerFile() {
fmt.Println("gin test...")
truerouter := gin.Default()

// 似乎这样做,templates下只能有文件,不能有目录
router.LoadHTMLGlob("templates/*")

// 将真实目录做不同前缀,方便引用css js等文件
// 有些自实现的用html文件或js文件,用html前缀。
router.StaticFS("/js", http.Dir("static/js"))
router.StaticFS("/css", http.Dir("static/css"))
router.StaticFS("/html", http.Dir("templates"))

router.GET("/", HandleIndex)
router.GET("/index", HandleIndex)
router.GET("/about", HandleAbout)
router.GET("/login", HandleLogin)

truerouter.Run(":8081")
}

其中 LoadHTMLGlob 用于加载 html 模板文件,StaticFS 指定静态资源,有两个参数,第一个指定前缀名称(即在 html 或 js 文件中引用时使用的路径,第二个指定真实路径(相对于 web 程序所在目录)。GET 函数用于响应对应的页面,由于响应函数非本文重点,故简单列举如下:

1
2
3
4
5
6
func HandleIndex(ctx *gin.Context) {
file := path.Join(gPrefix, "index.html")
ctx.HTML(http.StatusOK, file, gin.H{
truetrue"title": "Main website",
true})
}

注:gPrefix 将在下文提及。

整合方式

使用 bindata 方式,与上面示例没有本质区别,只是需要手动设置模板加载规则,指定静态资源文件方式也不同。主要代码如下:

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
41
42
43
44
45
46
47
48
// 整合文件形式
func webServerBindata() {
fmt.Println("gin test.....")
truerouter := gin.Default()
truet, err := loadTemplate()
trueif err != nil {
truetruepanic(err)
true}
truerouter.SetHTMLTemplate(t)

// 下面指定的是静态资源文件,与响应get/post的地址无关系
fsjs := assetfs.AssetFS{
Asset: bindata.Asset,
AssetDir: bindata.AssetDir,
AssetInfo: bindata.AssetInfo,
Prefix: "static/js",
Fallback: "index.html",
}
router.StaticFS("/js", &fsjs)

fscss := assetfs.AssetFS{
Asset: bindata.Asset, AssetDir: bindata.AssetDir, AssetInfo: bindata.AssetInfo,
Prefix: "static/css",
Fallback: "index.html",
}
router.StaticFS("/css", &fscss)

fshtml := assetfs.AssetFS{
Asset: bindata.Asset, AssetDir: bindata.AssetDir, AssetInfo: bindata.AssetInfo,
Prefix: "templates",
Fallback: "index.html",
}
router.StaticFS("/html", &fshtml)


router.StaticFS("/favicon.ico", &fshtml)

// 人为添加前缀,因为前面loadTemplate加载的html路径包含有路径前缀,因此加上
// 如果手动删除,则此处不需要前缀
//gPrefix = "templates"

router.GET("/", HandleIndex)
router.GET("/index", HandleIndex)
router.GET("/about", HandleAbout)
router.GET("/login", HandleLogin)

truerouter.Run(":8081")
}

自定义模板加载函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func loadTemplate() (*template.Template, error) {
fmt.Println("load my template")
truet := template.New("")
filenames := bindata.AssetNames()
for _, name := range filenames {
if !strings.HasSuffix(name, ".tmpl") && !strings.HasSuffix(name, ".html") &&
!strings.HasSuffix(name, ".css") && !strings.HasSuffix(name, ".js") &&
!strings.HasSuffix(name, ".ico") {
truetruetruecontinue
truetrue}
//fmt.Println("got html file: ", name)
content, err := bindata.Asset(name)
if err != nil {
return nil, err
}
t, err = t.New(name).Parse(string(content))
truetrueif err != nil {
truetruetruereturn nil, err
truetrue}
true}
truereturn t, nil
}

由于在生成 bindata.go 时,指定了2处目录,而-prefix选项只能指定一个前缀,于是干脆不加该参数,因此,生成的代码中,前缀也会出现对应的目录,正因为这样,代码才使用了gPrefix = "templates"手动指定前缀。后来手动删除生成代码的前缀,命令如下:

1
2
sed -i 's/templates\///g' bindata/bindata.go
sed -i 's/static\///g' bindata/bindata.go

两者关键代码对比如下图所示(注:左侧为未删除前缀的代码)。

笔者阅读 go-bindata-assetfs 代码尝试添加参数达到目的,未果。

参考

https://jaycechant.info/2020/go-bindata-golang-static-resources-embedding/
http://blog.hotsun168.com/index.php/archives/18/
1.16 版本新方法:
https://www.flysnow.org/2021/02/28/golang-embed-for-web.html

2021.5.5 凌晨