Static files
Go’s standard library can serve static files the same as a standalone web server like Apache or nginx.
Serving files
All files in a directory
You can use the FileServer function as the handler in ListenAndServe to serve files from a directory. It returns a If-Modified-Since HTTP header and 304 Not Modified response if the file is already cached on the user’s machine:
http.Dirimplements theFileSysteminterface. This means that the path you pass the function is treated like the root directory on disk that the program serves files from.
In this example, the app serves files from thefile/directory. You do not need to specify a file because it serves all in that directory. If you were to go tolocalhost:8080, you would see a list of links to the files in the directory. When you clicked the link, you are sent tolocalhost:8080/<filename>.html.- Use
FileServeras the server’s handler.
func main() {
dir := http.Dir("./files") // 1
if err := http.ListenAndServe(":8080", http.FileServer(dir)); err != nil { // 2
panic(err)
}
}
Single file
You can use ServeFile to serve a specific file with a handler. This example registers a handler with the DefaultMux server. The handler uses ServeFile to serve a file named hello.html at the web root path (/):
ServeFiletakes the response, request, and a file or directory string as its arguments.
func main() {
http.HandleFunc("/", hello)
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./files/hello.html")
}
Subdirectories
- Defines a root directory on your app’s filesystem. This root directory is where you store static files to serve.
- This line contains a few components:
http.FileServerreturns a handler that serves files from the given directory (./files/).StripPrefixreturns a handler that removes the given string from the path before it serves HTTP requests. Here, it removes/static/from the path. It also accepts a handler, which is theFileServer(dir)handler.For example, if you are serving CSS from
/static/style.css,StripPrefixchanges the path from/static/style.cssto/style.css, and thenFileServerlooks for the file in./files/style.css. So, if you go tohttp://localhost:8080/static/style.css, the server serves./files/style.cssbut still displayshttp://localhost:8080/static/style.cssin the address bar.This line could be rewritten with the following ways:
dir := http.Dir("./files/") fs := http.FileServer(dir) fs = http.StripPrefix("/files/", fs) http.Handle("/files/", handler) http.ListenAndServe(":8080", nil)
- Register the
/static/path with the handler that serves the static files.
func main() {
dir := http.Dir("./files/") // 1
handler := http.StripPrefix("/static/", http.FileServer(dir)) // 2
http.Handle("/static/", handler) // 3
http.HandleFunc("/", hello)
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
func hello(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "Hello, World!")
}
From an alternate location
You might need to serve your styles or JS from a CDN to increase performance. This example lets you pass the location of the stylesheet as a command-line argument:
- Define a flag with the default stylesheet location. For example, in your dev environment.
- The template string adds a
Locationin place of thehrefURL. - Create an anonymous struct that contains the template data.
- Parse the flags and template before
mainruns.
var t *template.Template
var l = flag.String("location", "http://localhost:8080", "A location") // 1
var tpl = `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Hello!</title>
<link rel="stylesheet" href="{{.Location}}/style.css"> // 2
</head>
<body>
<h1>This is a test!</h1>
</body>
</html>`
func serveStyles(w http.ResponseWriter, r *http.Request) {
data := struct{ Location *string }{ // 3
Location: l,
}
t.Execute(w, data)
}
func init() {
flag.Parse() // 4
t = template.Must(template.New("page").Parse(tpl))
}
func main() {
http.HandleFunc("/styles", serveStyles)
if err := http.ListenAndServe(":8080", nil); err != nil {
panic(err)
}
}
Embedding files in a binary
You might need to store non-executable files in a binary, such as images or stylesheets, directly in the Go binary. This means that your program does not have to locate the files in the host’s file system. Embedding files keeps your application self-contained, which is great for the following use cases:
- Container image or CLI tool: Limi the number of resources that needs to be built into the image or tool.
- Doc servers: Bundle markdown files into the binary to serve.
- Web UI for CLI apps: Monitoring or db tools can ship wit a built-in web UI.
- Improved portability: No risk of missing critical files.
Filesystem
The following example embeds a filesystem in your binary. It converts a file to bytes or a string, stores that data in a variable, then it uses the variable ot serve the file with the ServeContent method.
Here is a basic example:
- At build time, the compiler takes the
files/directory and embeds it in the binary. The embedded value is defined in the line directly below thego:embeddirective. These files are served from the<server-IP>:<port>/files/<filename>directory. For example, you can viewstyle.cssathttp://localhost:8080/files/style.csswith the following project structure:Theproject-root ├── files │ ├── hello.html │ ├── style.css │ └── test.html ├── go.mod └── main.go//go:embeddirective cannot exist within a function—it is for the compiler only. embed.FSis a filesystem type that lets you access embedded files like a read-only filesystem.fis the in-memory representation of that filesystem.http.FSis an adapter that converts theembed.FSinto aFileSystemimplementation.
//go:embed files // 1
var f embed.FS // 2
func main() {
if err := http.ListenAndServe(":8080", http.FileServer(http.FS(f))); err != nil { // 3
panic(err)
}
}
Single string
You can also embed a single file and store its contents in a string. This has the following use cases:
- Inline templates.
- Configuration file, so your app always has fallback settings.
- License or EULA, such as a legal notice or README.
- SQL queries that you can load at runtime instead of shipping
.sqlfiles.
As a simple demonstration, this example reads hello.html, stores it in a variable, then logs the variable to STDOUT:
//go:embed files/hello.html
var myString string
func main() {
log.Println(fmt.Sprintf("embedded value: %s", myString))
}
Config files
Here is an example of an embedded JSON configuration file:
- Embed the config file as a
string. - Create a struct for the config file contents.
- Parse the embedded config into a struct with
json.Unmarshal. - Use the config. Here, we print its contents to STDOUT.
import (
_ "embed"
"encoding/json"
"fmt"
"log"
)
//go:embed config.json
var configData string // 1
type Config struct { // 2
Server string `json:"server"`
Port int `json:"port"`
Username string `json:"username"`
Password string `json:"password"`
}
func main() {
var cfg Config // 3
if err := json.Unmarshal([]byte(configData), &cfg); err != nil {
log.Fatalf("error parsing config: %v", err)
}
fmt.Printf("Connecting to %s:%d as %s\n", // 4
cfg.Server,
cfg.Port,
cfg.Username
)
}