I wanted to create a small standalone web application in Go, the opposite of a regular web application where resources would be served separately via a CDN or HTTP server such as Nginx.
However, if performance is not a critical concern or the application is intended for low traffic, using a standalone application simplifies deployment and distribution because it's just an executable binary.
Several packages are available for embedding resources into a Go application:
I won't delve into the specifics of each library, but I prefer the bindata approach due to its ease of use and active support.
First, let's create an index.html
inside the frontend/
directory in your project:
<html>
<body>
Hello, World!
</body>
</html>
Now that we have a project setup and a static resource to test, let's install bindata using the command:
go get -u github.com/jteeuwen/go-bindata/...
We're ready to run the backend code of the web application. Create a main.go
file, and copy the following code:
package main
import (
"bytes"
"io"
"net/http"
)
//go:generate go-bindata -prefix "frontend/" -pkg main -o bindata.go frontend/...
func static_handler(rw http.ResponseWriter, req *http.Request) {
var path string = req.URL.Path
if path == "" {
path = "index.html"
}
if bs, err := Asset(path); err != nil {
rw.WriteHeader(http.StatusNotFound)
} else {
var reader = bytes.NewBuffer(bs)
io.Copy(rw, reader)
}
}
func main() {
http.Handle("/", http.StripPrefix("/", http.HandlerFunc(static_handler)))
http.ListenAndServe(":3000", nil)
}
Important line in this code:
//go:generate go-bindata -prefix "frontend/" -pkg main -o bindata.go frontend/...
The line above allows us to run the go-bindata
command when go generate
is called. As of version 1.4, you can run custom commands during the generation phase. It's just a matter of adding //go:generate command argument...
to your go file.
The go-bindata
command line has several parameters, so check the documentation on how to use it. In our case, we say:
-prefix "frontend/"
define part of the pathname for static
-pkg main
define the package name used in the generated code
-o bindata.go
define the name of the generated file
After running the go generate
command, you should see a generated file named bindata.go
. The structure of your project should look like this:
.
│
├── bindata.go (auto-generated file)
├── frontend
│ └── index.html
└── main.go
The logic for serving static files is in the static_handler
function, which receives the request and checks if the path matches the static path. The check is done using the Asset
function, automatically exported by the bindata.go
user. If the resource does not exist, we return a 404
, otherwise, we return the contents of the resource.
The rest of the code is for creating the web application and binding our static_handler
template to match all incoming requests for /
. If you have trouble understanding this code, check out the official Go documentation for http package
.
As a quick reminder of how Go handles packages, all identifiers will be automatically exported to other packages with the same name if the first letter of the identifier name starts with a capital letter.
Based on this rule, the bindata.go
file provides the Asset
function for the main
package. This loads and returns an asset for the given name. It returns an error if the resource cannot be found or cannot be loaded.