在 Go 程序中捆绑静态资源的最佳方式是什么?

新手上路,请多包涵

我正在使用 Go 开发一个小型 Web 应用程序,该应用程序旨在用作开发人员机器上的工具,以帮助调试他们的应用程序/Web 服务。该程序的界面是一个网页,其中不仅包括 HTML,还包括一些 JavaScript(用于功能)、图像和 CSS(用于样式)。我正计划开源这个应用程序,这样用户就可以运行一个 Makefile,所有的资源都会去他们需要去的地方。但是,我也希望能够简单地分发具有尽可能少的文件/依赖项的可执行文件。 有没有一种将 HTML/CSS/JS 与可执行文件捆绑在一起的好方法,这样用户只需要下载一个文件就可以了?


现在,在我的应用程序中,提供静态文件看起来有点像这样:

 // called via http.ListenAndServe
func switchboard(w http.ResponseWriter, r *http.Request) {

    // snipped dynamic routing...

    // look for static resource
    uri := r.URL.RequestURI()
    if fp, err := os.Open("static" + uri); err == nil {
        defer fp.Close()
        staticHandler(w, r, fp)
        return
    }

    // snipped blackhole route
}


所以这很简单:如果请求的文件存在于我的静态目录中,调用处理程序,它只是打开文件并尝试在服务之前设置好 Content-Type 。我的想法是,没有理由这需要基于真实的文件系统:如果有编译资源,我可以简单地通过请求 URI 索引它们并按原样提供它们。

让我知道是否没有好的方法可以做到这一点,或者我试图这样做是在树错树。我只是认为最终用户会希望管理的文件越少越好。

如果有比 go 更合适的标签,请随时添加或告诉我。

原文由 Jimmy Sawczuk 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 552
2 个回答

go-bindata 包看起来可能是您感兴趣的。

https://github.com/go-bindata/go-bindata

它将允许您将任何静态文件转换为可以嵌入代码中的函数调用,并在调用时返回文件内容的字节片。

原文由 Daniel 发布,翻译遵循 CC BY-SA 3.0 许可协议

从 Go 1.16 开始,go 工具支持直接在可执行二进制文件中嵌入静态文件。

您必须导入 embed 包,并使用 //go:embed 指令来标记要嵌入的文件以及要将它们存储到哪个变量中。

hello.txt 文件嵌入可执行文件的 3 种方法:

 import "embed"

//go:embed hello.txt
var s string
print(s)

//go:embed hello.txt
var b []byte
print(string(b))

//go:embed hello.txt
var f embed.FS
data, _ := f.ReadFile("hello.txt")
print(string(data))

使用 embed.FS 变量类型,您甚至可以将多个文件包含到一个变量中,这将提供一个简单的文件系统接口:

 // content holds our static web server content.
//go:embed image/* template/*
//go:embed html/index.html
var content embed.FS

net/http 支持从值 embed.FS 使用 http.FS() 提供文件,如下所示:

 http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(content))))

The template packages can also parse templates using text/template.ParseFS() , html/template.ParseFS() functions and text/template.Template.ParseFS() , html/template.Template.ParseFS() methods:

 template.ParseFS(content, "*.tmpl")

以下答案列出了您的旧选项(Go 1.16 之前)。


嵌入文本文件

如果我们谈论的是文本文件,它们可以很容易地嵌入源代码本身。只需使用反引号来声明 string 文字,如下所示:

 const html = `
<html>
<body>Example embedded HTML content.</body>
</html>
`

// Sending it:
w.Write([]byte(html))  // w is an io.Writer

优化提示:

由于大多数时候您只需要将资源写入 io.Writer ,您还可以存储 []byte 转换的结果:

 var html = []byte(`
<html><body>Example...</body></html>
`)

// Sending it:
w.Write(html)  // w is an io.Writer

唯一需要注意的是原始字符串文字不能包含反引号字符 (`)。原始字符串文字不能包含序列(与解释字符串文字不同),因此如果要嵌入的文本确实包含反引号,则必须断开原始字符串文字并将反引号连接为解释字符串文字,如本例所示:

 var html = `<p>This is a back quote followed by a dot: ` + "`" + `.</p>`

性能不受影响,因为这些连接将由编译器执行。

嵌入二进制文件

存储为字节片

对于二进制文件(例如图像),最紧凑(关于生成的本机二进制文件)和最有效的是将文件的内容作为 []byte 在您的源代码中。这可以由 go-bindata 等第三方工具/库生成。

如果你不想为此使用第 3 方库,这里有一个简单的代码片段,它读取一个二进制文件,并输出 Go 源代码,声明一个变量类型 []byte 将用文件的确切内容:

 imgdata, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}

fmt.Print("var imgdata = []byte{")
for i, v := range imgdata {
    if i > 0 {
        fmt.Print(", ")
    }
    fmt.Print(v)
}
fmt.Println("}")

如果文件包含从 0 到 16 的字节,则输出示例(在 Go Playground 上尝试):

 var imgdata = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15}

存储为 base64 string

如果文件不是“太大”(大多数图像/图标都符合条件),还有其他可行的选择。您可以将文件的内容转换为 Base64 string 并将其存储在源代码中。在应用程序启动时( func init() )或需要时,您可以将其解码为原始 []byte 内容。 Go 在 encoding/base64 包中很好地支持 Base64 编码。

将(二进制)文件转换为 base64 string 非常简单:

 data, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}
fmt.Println(base64.StdEncoding.EncodeToString(data))

将结果 base64 字符串存储在您的源代码中,例如作为 const

解码它只是一个函数调用:

 const imgBase64 = "<insert base64 string here>"

data, err := base64.StdEncoding.DecodeString(imgBase64) // data is of type []byte

按引用存储 string

比存储为 base64 更有效,但在源代码中可能更长存储二进制数据的 引用 字符串文字。我们可以使用 strconv.Quote() 函数获取任何字符串的引用形式:

 data, err := ioutil.ReadFile("someimage.png")
if err != nil {
    panic(err)
}
fmt.Println(strconv.Quote(string(data))

对于包含从 0 到 64 的值的二进制数据,这是输出的样子(在 Go Playground 上试试):

"\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?"

(请注意 strconv.Quote() 附加了一个引号。)

您可以在源代码中直接使用这个带引号的字符串,例如:

 const imgdata = "\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?"

即开即用,无需解码;取消引用由 Go 编译器在编译时完成。

如果你需要它,你也可以将它存储为字节片:

 var imgdata = []byte("\x00\x01\x02\x03\x04\x05\x06\a\b\t\n\v\f\r\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f !\"#$%&'()*+,-./0123456789:;<=>?")

原文由 icza 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题