提前试用将在 Go1.16 中发布的内嵌静态资源功能

大家好 , 我是站长 polarisxu 。
喜欢 Go 语言有很多理由 , 其中有一点“因为编译为一个二进制文件 , 直接运行 , 没有其他依赖 , 使得部署特别容易 。 ”我想是很多人喜欢的 。
然而一个项目 , 很可能会包含一些静态资源文件 , 这样一来 , 一个 Go 二进制文件就不能解决了 , 需要将静态资源文件一起带上 。 于是有了很多第三方解决方案 , 将静态资源文件“嵌入”最终的 Go 二进制文件中 。 最知名的应该是 go-bindata , 此外还有很多其他的:

  • github.com/alecthomas/gobundle
  • github.com/GeertJohan/go.rice
  • github.com/go-playground/statics
  • github.com/gobuffalo/packr
  • github.com/knadh/stuffbin
  • github.com/mjibson/esc
  • github.com/omeid/go-resources
  • github.com/phogolabs/parcello
  • github.com/pyros2097/go-embed
  • github.com/rakyll/statik
  • github.com/shurcooL/vfsgen
  • github.com/UnnoTed/fileb0x
  • github.com/wlbr/templify
  • perkeep.org/pkg/fileembed
从这个列表足以看出需求的广泛性 。 于是官方决定提供实现 , 在 go 命令中实现该功能 。 因为在 Go 命令中添加对嵌入基本功能的直接支持将消除对某些工具的需求 , 至少可以简化其他工具的实现 。
2020 年 10 月 30 日 , Russ Cox 提交了最终的实现:[cmd/go: add //go:embed support](cmd/go: add //go:embed support) , 意味着你在 tip 版本可以试用该功能了 。 Go1.16 版本会包含该功能 。 欢迎大家试用 , 反馈建议 。
01 试用 go embed通过几个示例快速了解 go embed 的用法 。
例 1:内嵌文件 — Web 应用基于 Echo 框架:
package mainimport ( _ "embed" "net/http" "github.com/labstack/echo")//go:embed static/logo.pngvar content []bytefunc main() { e := echo.New() e.GET("/", func(c echo.Context) error {return c.Blob(http.StatusOK, "image/png", content) }) e.Logger.Fatal(e.Start(":8989"))}目录结构如下:
.├── main.go└── static└── logo.png编译运行后 , 可以将二进制文件移到任何地方运行 , 浏览器访问 http://localhhost:8989 , 能够正确显示 logo 图片表示成功了 。
基于 Gin 框架 , 代码类似:
package mainimport (_ "embed""net/http""github.com/gin-gonic/gin")//go:embed static/logo.pngvar content []bytefunc main() {router := gin.Default()router.GET("/", func(ctx *gin.Context) {ctx.Data(http.StatusOK, "image/png", content)})router.Run(":8989")}直接使用 net/http 库 , 代码如下:
package mainimport (_ "embed""log""net/http""fmt")//go:embed static/logo.pngvar content []bytefunc main() {http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {w.Header().Add("Content-Type", "image/png")w.WriteHeader(http.StatusOK)fmt.Fprintf(w, "%s", content)})log.Fatal(http.ListenAndServe(":8989", nil))}例 2:内嵌文件 — 命令行应用简单的 Hello World:
package mainimport (_ "embed""fmt")//go:embed message.txtvar message stringfunc main() {fmt.Println(message)}其中 messaeg.txt 中的内容是 Hello World 。 目录结构如下:
.├── main.go└── message.txt编译后 , 可以将二进制移到任何地方 , 运行输出 Hello World(即 messaeg.txt 中的内容) 。
例 3:内嵌目录 - 命令行应用以下程序将 static 目录内嵌到二进制程序中 , 然后在当前目录创建 static 目录中的所有文件 。
package mainimport ( "embed" "io" "log" "os" "path")//go:embed staticvar local embed.FSfunc main() { fis, err := local.ReadDir("static") if err != nil {log.Fatal(err) } for _, fi := range fis {in, err := local.Open(path.Join("static", fi.Name()))if err != nil {log.Fatal(err)}out, err := os.Create("embed-" + path.Base(fi.Name()))if err != nil {log.Fatal(err)}io.Copy(out, in)out.Close()in.Close()log.Println("exported", "embed-"+path.Base(fi.Name())) }}该示例的目录结构和例 1 一样 。 编译后 , 可以将二进制文件移到任何地方 , 运行后 , 会在当前目录输出以 embed- 开头的文件 。
例 4:内嵌目录 — Web 应用基于 Echo 框架:
package mainimport ("embed""net/http""github.com/labstack/echo/v4")//go:embed staticvar local embed.FSfunc main() {e := echo.New()e.GET("/*", echo.WrapHandler(http.FileServer(http.FS(local))))e.Logger.Fatal(e.Start(":8989"))}同样 , 目录结构和 example1 一致 。 编译后运行 , 访问 http://localhost:8989 , 看到如下界面:
提前试用将在 Go1.16 中发布的内嵌静态资源功能文章插图
注意上面使用的是 /* , 如果直接使用 / , 点击链接会是 404 。
换成 Gin , 代码如下:
package mainimport ("embed""net/http""github.com/gin-gonic/gin")//go:embed static/*var local embed.FSfunc main() {router := gin.Default()router.GET("/*filepath", gin.WrapH(http.FileServer(http.FS(local))))router.Run(":8989")}