前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[golang]Go内嵌静态资源go-bindata的安装及使用

[golang]Go内嵌静态资源go-bindata的安装及使用

作者头像
landv
发布2019-09-25 15:57:22
5.8K0
发布2019-09-25 15:57:22
举报
文章被收录于专栏:landvlandvlandv

使用 Go 开发应用的时候,有时会遇到需要读取静态资源的情况。比如开发 Web 应用,程序需要加载模板文件生成输出的 HTML。在程序部署的时候,除了发布应用可执行文件外,还需要发布依赖的静态资源文件。这给发布过程添加了一些麻烦。既然发布单独一个可执行文件是非常简单的操作,就有人会想办法把静态资源文件打包进 Go 的程序文件中。下面就来看一些解决方案:

go-bindata

go-bindata 是目前我的程序 pugo 在用的嵌入静态资源的工具。它可以把静态文件嵌入到一个 go 文件中,并提供一些操作方法。

安装 go-bindata: go get -u github.com/jteeuwen/go-bindata/...

注意 go get 地址最后的三个点 ...。这样会分析所有子目录并下载依赖编译子目录内容。go-bindata 的命令工具在子目录中。(还要记得把 $GOPATH/bin 加入系统 PATH)。

使用命令工具 go-bindata ( pugo 的例子):

go-bindata -o=app/asset/asset.go -pkg=asset source/... theme/... doc/source/... doc/theme/...

-o 输出文件到 app/asset/asset.go,包名 -pkg=asset,然后是需要打包的目录,三个点包括所有子目录。这样就可以把所有相关文件打包到 asset.go 且开头是 package asset 保持和目录一致。

pugo 里释放静态文件的代码:

dirs := []string{"source", "theme", "doc"} // 设置需要释放的目录

for _, dir := range dirs {
    // 解压dir目录到当前目录
    if err := asset.RestoreAssets("./", dir); err != nil {
        isSuccess = false
        break
    }
}
if !isSuccess {
    for _, dir := range dirs {
        os.RemoveAll(filepath.Join("./", dir))
    }
}

asset.go 内的静态内容还是根据实际的目录位置索引。所以我们可以直接通过目录或者文件地址去操作。

-debug 开发模式

go-bindata 支持开发模式,即不嵌入静态内容,只生成操作方法到输出的 go 代码中,如:

go-bindata -debug -o=app/asset/asset.go -pkg=asset source/... theme/... doc/source/... doc/theme/...

-debug 参数开启开发模式。生成的代码会直接去读取静态文件到内存,而不是编码到代码中。代码文件更小,你更快速的编写业务逻辑。

// -pkg=asset, 打包的包名是 asset
bytes, err := asset.Asset("theme/default/post.html")    // 根据地址获取对应内容
if err != nil {
    fmt.Println(err)
    return
}
t, err := template.New("tpl").Parse(string(bytes))      // 比如用于模板处理
fmt.Println(t, err)
http.FileSystem

http.FileSystem 是定义 HTTP 静态文件服务的接口。go-bindata 的第三方包 go-bindata-assetfs 实现了这个接口,支持 HTTP 访问静态文件目录的行为。以我们上面编译好的 asset.go 为例:

import (
    "net/http"

    "github.com/elazarl/go-bindata-assetfs"
    "github.com/go-xiaohei/pugo/app/asset" // 用 pugo 的asset.go进行测试
)

func main() {
    fs := assetfs.AssetFS{
        Asset:     asset.Asset,
        AssetDir:  asset.AssetDir,
        AssetInfo: asset.AssetInfo,
    }
    http.Handle("/", http.FileServer(&fs))
    http.ListenAndServe(":12345", nil)
}

访问 http://localhost:12345,就可以看到嵌入的 source,theme,doc 的目录列表页面,和 Nginx 查看静态文件目录一样的。

go.rice

go.rice 也支持打包静态文件到 go 文件中,但是行为和 go-bindata 很不相同。从使用角度,go.rice 其实是更便捷的静态文件操作库。打包静态文件反而是顺带的功能。

安装和 go-bindata 一样,注意 三个点

go get github.com/GeertJohan/go.rice/...

go.rice 把一个目录认为是一个 rice.Box 操作:

import (
    "fmt"
    "html/template"

    "github.com/GeertJohan/go.rice"
)

func main() {
    // 这里写相对于的执行文件的地址
    box, err := rice.FindBox("theme/default")
    if err != nil {
        println(err.Error())
        return
    }
    // 从目录 Box 读取文件
    str, err := box.String("post.html")
    if err != nil {
        println(err.Error())
        return
    }
    t, err := template.New("tpl").Parse(str)
    fmt.Println(t, err)
}

rice 命令

go.rice 的打包命令是 rice。用起来非常直接:在有使用 go.rice 操作的 go 代码目录,直接执行 rice embed-go:

rice embed-go rice -i "github.com/fuxiaohei/xyz" embed-go // -i 处理指定包里的 go.rice 操作

他就会生成当前包名下的、嵌入了文件的代码 rice-box.go。但是,它不递归处理 import。他会分析当前目录下的 go 代码中 go.rice 的使用,找到对应需要嵌入的文件夹。但是子目录下的和 import 的里面的 go.rice 使用不会分析,需要你手动 cd 过去或者 -i 指定要处理的包执行命令。这点来说非常的不友好。

http.FileSystem

go.rice 是直接支持 http.FileSystem 接口:

func main() {
    // MustFindBox 出错直接 panic
    http.Handle("/", http.FileServer(rice.MustFindBox("theme").HTTPBox()))
    http.ListenAndServe(":12345", nil)
}

有点略繁琐的是 rice.FindBox(dir) 只能加载一个目录。因此需要多个目录的场景,会有代码:

func main() {
    http.Handle("/img", http.FileServer(rice.MustFindBox("static/img").HTTPBox()))
    http.Handle("/css", http.FileServer(rice.MustFindBox("static/css").HTTPBox()))
    http.Handle("/js", http.FileServer(rice.MustFindBox("static/js").HTTPBox()))
    http.ListenAndServe(":12345", nil)
}

esc

esc 的作者在研究几款嵌入静态资源的工具后,发觉都不好用,就自己写出了 esc。它的需求很简单,就是嵌入静态资源 和 支持 http.FileSystemesc 工具也这两个主要功能。

安装 esc:

go get github.com/mjibson/esc

使用方法和 go-bindata 类似:

// 注意 esc 不支持 source/... 三个点表示所有子目录 go-bindata -o=asset/asset.go -pkg=asset source theme doc/source doc/theme

直接支持 http.FileSystem

import (
    "net/http"
    "asset" // esc 生成 asset/asset.go 
)

func main() {
    fmt.Println(asset.FSString(false, "/theme/default/post.html"))         // 读取单个文件
    http.ListenAndServe(":12345", http.FileServer(asset.FS(false)))     // 支持 http.FileSystem,但是没有做展示目录的支持
}

esc 有个较大的问题是只能一个一个文件操作,不能文件夹操作,没有类似go-bindata 的 asset.RestoreDir() 方法。并且没有方法可以列出嵌入的文件的列表,导致也无法一个一个文件操作,除非自己写死。这是我不使用他的最大原因。

go generate

嵌入静态资源的工具推荐配合 go generate 使用。例如 pugo 的入口文件就有:

package main

import (
    "os"
    "time"

    "github.com/go-xiaohei/pugo/app/command"
    "github.com/go-xiaohei/pugo/app/vars"
    "github.com/urfave/cli"
)

//go:generate go-bindata -o=app/asset/asset.go -pkg=asset source/... theme/... doc/source/... doc/theme/...

// ......

在编译的时候执行:

go generate && go build

这个是 go generate 的基本用法。更详细的了解可以看 官方博文

总结

我在开发 pugo 的时候对这几款嵌入静态资源的程序进行了测试。go.rice 并不是我想要的模式,就没有考虑。esc 提供的操作方法太少,无法满足程序开发的需要。最后选择 go-bindata。但是 go-bindata 和 go.rice 都是将纯字符数据或 []byte 字符数据写入 go 文件,内容非常大。esc 是写入 gzip 压缩流的 Base64 编码。经过压缩后 go 代码的大小明显更少(我嵌入的都是模板等文本文件)。可见库类都有各自的优缺点。倘若有 go-bindata 那样丰富的 API,又有 esc 那样嵌入压缩过的字符数据,那该多好。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2019-09-24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • go-bindata
    • -debug 开发模式
    • go.rice
      • rice 命令
        • http.FileSystem
        • esc
        • go generate
        • 总结
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档