How to use go:embed in Golang

The embed package is a compiler command directive that allows a program to include all sorts of files and folders in the Go binary during compiling. The go:embed method authorizes us to embed the contents of a file into a defined variable directly. This method allows Golang to read the file’s content while compiling. The embed package is only available in Go 1.16 and later. To check the Go version, we can use the following Go command:

go version
Checking Go version

How does go:embed work?

The go:embed compiler directive and the embed package, together, include static files in a Go binary. It is done by putting the //go:embed comment above the declaration of the variable used for embedding.

//go:embed file_name
var fileName embed.FS
Comment used by go:embed

By this command, the compiler will search for a file with the specified name the file_name, and include its assets in the resulting binary. The declared variable the fileName after the go:embed directive will act as a container for the contents embedded. The variable can be of different types such as the string, []byte, or embed.FS, depending on how we want the embedded content to be handled. Through the following table, we can check which variable type should be used:

Variable Type

Access

Modifications

When to Use?

Example



string

The variable holds the embedded content as a string

In the Go application, resources embedded as string are treated as read-only.

When a single file such as configuration data, a small template, or a piece of text needs to be embedded


var fileContent string



[]byte

The variable ends up as a slice of bytes containing the embedded content

The content of the []byte variables is modifiable ,within the Go application, since they are just slices of bytes.

When individual files, especially binary files like images, fonts, or other non-text data, need to be embedded


var fileContent []byte


embed.FS

The variable can be accessed as a part of the file system

In the Go application, resources embedded using the embed.FS are read-only.

When multiple files or an entire directory needs to be embedded as a read-only file system


var fileName embed.FS

Embed a text file

Here, we’ll embed contents of a text file into a []byte. The file below is a text file named the test.txt. In this text file, we have inserted a list of things, as mentioned below:

List of things
Apple
Banana
Table
Chair
Random list of things in a test.txt file

Here is the Golang program that we will use to embed the text file:

main.go
test.txt
package main
import (
_ "embed"
"fmt"
)
var (
//go:embed test.txt
text_file []byte
)
func main() {
fmt.Println(string(text_file))
}

Code explanation

  • Line 1: The package main specifies that the Golang program has started, and the code written after this line is a part of the package main.

  • Lines 2–5: This shows import of necessary packages. Here, the fmt package is imported for printing, and the _ "embed". The _ before the "embed" is to tell the Go compiler that the package is being imported for initializing embedded content, instead of directly using it in code.

  • Lines 6–9: A variable named the text_file is declared which is of type []byte. The comment //go:embed test.txt is a command that instructs the Go compiler to embed contents of the test.txt into the text_file variable during compilation.

  • Lines 10–12: In the main function, the fmt.Println() function is used to print the content of the text_file and string(text_file) to convert byte slice to a string before printing it.

Embed static files

We can embed static files into a binary of a web application using the go:embed method. Here is an HTML file named the index.html that we will be embedding:

<!DOCTYPE html>
<html>
<head>
<title>Sample HTML Page</title>
</head>
<body>
<h1>Hello, World!</h1>
<p>Here is a sample static file embedded using Golang!</p>
</body>
</html>

The main.go file that we will use to embed a static file is as follows:

package main
import (
"embed"
"fmt"
"log"
"net/http"
"os"
"strings"
)
//go:embed public
var static_files embed.FS
var staticDir = "public"
func rootPath(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "no-cache")
if r.URL.Path == "/" {
r.URL.Path = fmt.Sprintf("/%s/", staticDir)
}
else {
b := strings.Split(r.URL.Path, "/")[0]
if b != staticDir {
r.URL.Path = fmt.Sprintf("/%s%s", staticDir, r.URL.Path)
}
}
h.ServeHTTP(w, r)
})
}
func main() {
var staticFS = http.FS(static_files)
fs := rootPath(http.FileServer(staticFS))
http.Handle("/", fs)
port := os.Getenv("PORT")
if port == "" {
port = "3000"
}
log.Printf("Listening on :%s...\n", port)
err := http.ListenAndServe(fmt.Sprintf(":%s", port), nil)
if err != nil {
log.Fatal(err)
}
}

Code explanation

  • Lines 10–11: The //go:embed directive indicates that public directory should be embedded to the static_files variable using the embed.FS type. It will be embedded during compilation and can be accessed via the static_files.

  • Line 13: The staticDir variable is declared with value public, which is the directory that will be used to serve static files.

  • Line 15: The func rootPath(h http.Handler) http.Handler introduces the rootPath() function, which is middleware for processing incoming HTTP requests to ensure proper URL paths. It takes the http.Handler as an argument and returns an http.Handler.

  • Line 16: The return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) defines an anonymous function that takes w (the http.ResponseWriter) and r (the http.Request) as parameters.

  • Line 17: The w.Header().Set("Cache-Control", "no-cache") sets the Cache-Control header in the response to the "no-cache", which is used to prevent the caching of static files.

  • Lines 19–21: This block of code checks if the URL path is /. If the URL path is /, it will redirect the request by modifying the URL path to the "/{staticDir}/". This will effectively redirecting requests to the staticDir route (e.g., from / to /public/).

  • Lines 22–23: The b := strings.Split(r.URL.Path, "/")[0] is executed if the URL path is not /. It checks if the first part of the URL path (before the first /) is not equal to the staticDir.

  • Lines 24–27: If the first part of the URL path is not equal to the staticDir, it adds the staticDir as a prefix to the URL path. This is done to ensure that requests are directed to the proper paths within the staticDir.

  • Line 28: The modified request r, along with response writer w, is passed to the next HTTP handler h for further processing.

  • Lines 29–30: The anonymous function is closed and the rootPath middleware function is returned.

  • Lines 32–49: In the main() function, the static_files variable is converted into http.FS type and used to create an http.FileServer. The rootPath middleware is applied to the file server for it to handle URL path modifications. The root path is handled by the fs file server, and is set up to listen on the specified port in PORT. If the PORT environment variable is not set, it defaults to port 3000. The server is started using the http.ListenAndServe(), and any errors are logged using the log.Fatal() function.

Note: We can embed multiple static files in the same way.

To practice hosting embedded static files, press "Run" below and access the webpage on the given link.

<!DOCTYPE html>
<html>
<head>
    <title>Sample HTML Page</title>
    <link rel="stylesheet" type="text/css" href="style.css">
</head>
<body>
    <h1>Hello World</h1>
    <p>Here is a sample static file embedded using Golang!</p>
</body>
</html>
Static file embedded using "go:embed" method

Advantages of the go:embed

We can find several advantages of the go:embed in the following table:

  • Simplicity: The go:embed simplifies embedding static assets, configuration files, and other resources into the Golang code.

  • Type safety: The go:embed provides type safety, ensuring that the embedded files are of the expected type (e.g., string, []byte, or embed.FS). This helps prevent runtime errors related to type mismatches.

  • Performance: Accessing embedded resources is often faster than reading files from disk, especially in scenarios where there are many small assets. This can improve the overall performance of the Go application.

  • Maintainability: Embedding resources in the Go code makes managing and versioning resources alongside source code easier. It simplifies the development workflow and reduces the risk of missing or mismatched assets.

Limitations of the go:embed

  • No dynamic embedding: The go:embed requires specifying the files or directories to embed at compile-time. We cannot dynamically embed files based on runtime conditions.

  • Read-only resources: Embedded resources are typically read-only within the application. While this benefits security and consistency, the embedded resources cannot be modified at runtime.

  • File size: Embedding large files or directories can significantly increase the size of the Go binary, potentially impacting startup times and memory usage.

  • No encryption: Embedded resources are not automatically encrypted or obfuscated. If they contain sensitive data, additional steps need to be taken to secure them.

Conclusion

Despite the limitations, the go:embed remains a convenient and resilient feature for embedding static files and resources in Go applications, especially for web development.

Copyright ©2024 Educative, Inc. All rights reserved