Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

go

The go-bindata package looks like it might be what you're interested in.

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.


Starting with Go 1.16 the go tool has support for embedding static files directly in the executable binary.

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.

3 ways to embed a hello.txt file into the executable:

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:

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

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

The following of the answer lists your old options (prior to 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:

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:

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 (e.g. 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. This can be generated by 3rd party toos/libraries like 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:

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):

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

Storing as 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. On application startup (func init()) or when needed, you can decode it to the original []byte content. Go has nice support for Base64 encoding in the encoding/base64 package.

Converting a (binary) file to base64 string is as simple as:

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, e.g. as a 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

More efficient than storing as base64, but may be longer in source code is storing the quoted string literal of the binary data. We can obtain the quoted form of any string using the strconv.Quote() function:

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):

"\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.)

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.

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

Bundle React application

For example, you have a build output from react like the following:

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 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:

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.

Credit to Amit Mittal.

Note: go:embed requires go 1.16 or higher.


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. In the case code just look like line below (and some example can be found here)

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

As a popular alternative to go-bindata mentioned in another answer, mjibson/esc also embeds arbitrary files, but handles directory trees particularly conveniently.