简体   繁体   English

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

[英]What's the best way to bundle static resources in a Go program?

I'm working on a small web application in Go that's meant to be used as a tool on a developer's machine to help debug their applications/web services.我正在使用 Go 开发一个小型 Web 应用程序,该应用程序旨在用作开发人员机器上的工具,以帮助调试他们的应用程序/Web 服务。 The interface to the program is a web page that includes not only the HTML but some JavaScript (for functionality), images, and CSS (for styling).该程序的界面是一个网页,不仅包括 HTML,还包括一些 JavaScript(用于功能)、图像和 CSS(用于样式)。 I'm planning on open-sourcing this application, so users should be able to run a Makefile, and all the resources will go where they need to go.我打算开源这个应用程序,所以用户应该能够运行一个 Makefile,所有的资源都会去他们需要去的地方。 However, I'd also like to be able to simply distribute an executable with as few files/dependencies as possible.但是,我还希望能够简单地分发具有尽可能少的文件/依赖项的可执行文件。 Is there a good way to bundle the HTML/CSS/JS with the executable, so users only have to download and worry about one file?有没有一种将 HTML/CSS/JS 与可执行文件捆绑在一起的好方法,这样用户只需下载并担心一个文件?


Right now, in my app, serving a static file looks a little like this:现在,在我的应用程序中,提供静态文件看起来有点像这样:

// 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
}
    

So it's pretty simple: if the requested file exists in my static directory, invoke the handler, which simply opens the file and tries to set a good Content-Type before serving.所以这很简单:如果请求的文件存在于我的静态目录中,则调用处理程序,它只是打开文件并尝试在服务之前设置一个好的Content-Type My thought was that there's no reason this needs to be based on the real filesystem: if there were compiled resources, I could simply index them by request URI and serve them as such.我的想法是没有理由这需要基于真实的文件系统:如果有已编译的资源,我可以简单地通过请求 URI 索引它们并照此提供它们。

Let me know if there's not a good way to do this or I'm barking up the wrong tree by trying to do this.让我知道是否没有好的方法可以做到这一点,或者我试图这样做是在吠叫错误的树。 I just figured the end-user would appreciate as few files as possible to manage.我只是认为最终用户会希望管理尽可能少的文件。

If there are more appropriate tags than , please feel free to add them or let me know.如果有比更合适的标签,请随时添加它们或让我知道。

Starting with Go 1.16 the go tool has support for embedding static files directly in the executable binary.从 Go 1.16 开始,go 工具支持将静态文件直接嵌入到可执行二进制文件中。

You have to import the embed package, and use the //go:embed directive to mark what files you want to embed and into which variable you want to store them.您必须导入embed包,并使用//go:embed指令来标记要嵌入哪些文件以及要将它们存储到哪个变量中。

3 ways to embed a hello.txt file into the executable: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))

Using the embed.FS type for the variable you can even include multiple files into a variable that will provide a simple file-system interface:对变量使用embed.FS类型,您甚至可以将多个文件包含到一个变量中,该变量将提供一个简单的文件系统接口:

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

The net/http has support to serve files from a value of embed.FS using http.FS() like this: net/http支持使用http.FS()embed.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:模板包还可以使用text/template.ParseFS()html/template.ParseFS()函数和text/template.Template.ParseFS()html/template.Template.ParseFS()方法解析模板:

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

The following of the answer lists your old options (prior to Go 1.16).以下答案列出了您的旧选项(在 Go 1.16 之前)。


Embedding Text Files嵌入文本文件

If we're talking about text files, they can easily be embedded in the source code itself.如果我们谈论的是文本文件,它们可以很容易地嵌入到源代码本身中。 Just use the back quotes to declare the string literal like this:只需使用反引号来声明string文字,如下所示:

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

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

Optimization tip:优化提示:

Since most of the times you will only need to write the resource to an io.Writer , you can also store the result of a []byte conversion:由于大多数时候您只需将资源写入io.Writer ,您还可以存储[]byte转换的结果:

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

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

Only thing you have to be careful about is that raw string literals cannot contain the back quote character (`).唯一需要注意的是原始字符串文字不能包含反引号字符 (`)。 Raw string literals cannot contain sequences (unlike the interpreted string literals), so if the text you want to embed does contain back quotes, you have to break the raw string literal and concatenate back quotes as interpreted string literals, like in this example:原始字符串文字不能包含序列(与解释的字符串文字不同),因此如果要嵌入的文本确实包含反引号,则必须打破原始字符串文字并将反引号连接为解释字符串文字,如下例所示:

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

Performance is not affected, as these concatenations will be executed by the compiler.性能不受影响,因为这些连接将由编译器执行。

Embedding Binary Files嵌入二进制文件

Storing as a byte slice存储为字节片

For binary files (eg images) most compact (regarding the resulting native binary) and most efficient would be to have the content of the file as a []byte in your source code.对于二进制文件(例如图像),最紧凑(关于生成的本机二进制文件)和最有效的方法是将文件的内容作为源代码中的[]byte This can be generated by 3rd party toos/libraries like go-bindata .这可以由go-bindata等第三方工具/库生成。

If you don't want to use a 3rd party library for this, here's a simple code snippet that reads a binary file, and outputs Go source code that declares a variable of type []byte that will be initialized with the exact content of the file:如果您不想为此使用第 3 方库,这里有一个简单的代码片段,它读取二进制文件,并输出声明[]byte类型变量的 Go 源代码,该变量将使用文件:

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("}")

Example output if the file would contain bytes from 0 to 16 (try it on the Go Playground ):如果文件包含从 0 到 16 的字节的示例输出(在Go Playground上尝试):

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

Storing as base64 string存储为 base64 string

If the file is not "too large" (most images/icons qualify), there are other viable options too.如果文件不是“太大”(大多数图像/图标都符合条件),那么还有其他可行的选项。 You can convert the content of the file to a Base64 string and store that in your source code.您可以将文件的内容转换为 Base64 string并将其存储在源代码中。 On application startup ( func init() ) or when needed, you can decode it to the original []byte content.在应用程序启动时( func init() )或需要时,您可以将其解码为原始[]byte内容。 Go has nice support for Base64 encoding in the encoding/base64 package. Go 在encoding/base64包中对 Base64 编码有很好的支持。

Converting a (binary) file to base64 string is as simple as:将(二进制)文件转换为 base64 string非常简单:

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

Store the result base64 string in your source code, eg as a const .将结果 base64 字符串存储在您的源代码中,例如作为const

Decoding it is just one function call:解码它只是一个函数调用:

const imgBase64 = "<insert base64 string here>"

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

Storing as quoted string存储为带引号的string

More efficient than storing as base64, but may be longer in source code is storing the quoted string literal of the binary data.比存储为 base64 更有效,但在源代码中可能更长的是存储二进制数据的带引号的字符串文字。 We can obtain the quoted form of any string using the strconv.Quote() function:我们可以使用strconv.Quote()函数获取任何字符串的引号形式:

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

For binary data containing values from 0 up to 64 this is how the output would look like (try it on the Go Playground ):对于包含从 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:;<=>?"

(Note that strconv.Quote() appends and prepends a quotation mark to it.) (请注意, strconv.Quote()会在其前面附加一个引号。)

You can directly use this quoted string in your source code, for example:您可以在源代码中直接使用这个带引号的字符串,例如:

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:;<=>?"

It is ready to use, no need to decode it;即用即用,无需解码; the unquoting is done by the Go compiler, at compile time.取消引用是由 Go 编译器在编译时完成的。

You may also store it as a byte slice should you need it like that:如果您需要它,您也可以将其存储为字节片:

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:;<=>?")

The go-bindata package looks like it might be what you're interested in. go-bindata 包看起来可能是您感兴趣的。

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

It will allow you to convert any static file into a function call that can be embedded in your code and will return a byte slice of the file content when called.它将允许您将任何静态文件转换为可嵌入代码中的函数调用,并在调用时返回文件内容的字节切片。

Bundle React application捆绑 React 应用程序

For example, you have a build output from react like the following:例如,您有一个来自 react 的构建输出,如下所示:

build/favicon.ico
build/index.html
build/asset-manifest.json
build/static/css/**
build/static/js/**
build/manifest.json

When you use go:embed like this, it will serve the contents as http://localhost:port/build/index.html which is not what we want (unexpected /build ).当你像这样使用go:embed时,它会将内容作为http://localhost:port/build/index.html提供,这不是我们想要的(意外的/build )。

//go:embed build/*
var static embed.FS

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

In fact, we will need to take one more step to make it works as expected by using fs.Sub :事实上,我们需要再采取一步,通过使用fs.Sub使其按预期工作:

package main

import (
    "embed"
    "io/fs"
    "log"
    "net/http"
)

//go:embed build/*
var static embed.FS

func main() {
    contentStatic, _ := fs.Sub(static, "build")
    http.Handle("/", http.FileServer(http.FS(contentStatic)))
    log.Fatal(http.ListenAndServe("localhost:8080", nil))
}

Now, http://localhost:8080 should serve your web application as expected.现在, http://localhost:8080应该按预期为您的 Web 应用程序提供服务。

Credit to Amit Mittal .归功于阿米特米塔尔

Note: go:embed requires go 1.16 or higher.注意: go:embed需要 go 1.16 或更高版本。

also there is some exotic way - I use maven plugin to build GoLang projects and it allows to use JCP preprocessor to embed binary blocks and text files into sources.还有一些奇特的方式——我使用maven 插件来构建 GoLang 项目,它允许使用JCP 预处理器将二进制块和文本文件嵌入到源代码中。 In the case code just look like line below ( and some example can be found here )在这种情况下,代码如下所示( 可以在此处找到一些示例

var imageArray = []uint8{/*$binfile("./image.png","uint8[]")$*/}

作为另一个答案中提到的go-bindata的流行替代方案, mjibson/esc还嵌入了任意文件,但特别方便地处理目录树。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在Go中实现信号量的最佳方法是什么? - What's the best way to implement a semaphore in Go 前往:检测无效JSON字符串字符的最佳方法是什么? - Go: What's the best way to detect invalid JSON string characters? 进行错误分组的最佳方法是什么? - What is the best way to group errors in go? 在 Go 中测试空字符串的最佳方法是什么? - What is the best way to test for an empty string in Go? 在机械(go)中路由任务的最佳方式是什么? - What is the best way to route tasks in machinery(go)? 在不耦合到 SQL 驱动程序的情况下,处理 Go 中的数据库错误的最佳方法是什么? - What's the best way to handle database errors in Go without getting coupled to the SQL driver? 在Go中维护未解析的JSON字段的最佳方法是什么? - What's the best way to maintain un-parsed JSON fields in Go? 当依赖项不公开接口时,在GO中模拟依赖项的最佳方法是什么 - What's the best way to mock a dependency in GO when the dependency doesn't expose an interface 使用 k8s.io/client-go 库更改 kubernetes 部署时获得通知的最佳方式是什么? - What's the best way to get notified when kubernetes Deployments change using the k8s.io/client-go library? 什么是同步goroutines的首选方式 - What is go's preferred way to synchronize goroutines
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM