趁着五一放假,趁着有时间,把欠的一些技术集中研究研究,写写文章,好给自己一个交待。 本文研究静态资源文件的在 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 凌晨