Package zipfs is an implementation of a Go http.FileSystem. Instead of serving files from a local filesystem directory, it serves the
contents of a provided zip file. This might be useful to you if you have a Golang HTTP server (perhaps an API server) and would like to
serve static content (perhaps a single-page application).
Hosting static content from a zip archive has several nice properties:
- Data is stored in compressed form (of course), which makes it relatively efficient for large static asset bundles.
- File metadata (particularly modification time) is preserved, which allows for caching.
- Because zip archive data can be prefixed with any amount of arbitrary data, you can actually embed it into your server's binary executable file by concatenating it. This is extremely convenient and is perhaps the most compelling use case. An example of this is below.
As described above, it is possible to embed zip data directly into your server's binary executable file. Here's some example code for this:
package main
import (
"log"
"net/http"
"gerace.dev/zipfs"
)
func main() {
fs, err := zipfs.NewEmbeddedZipFileSystem()
if err != nil {
log.Fatalf("Error setting up zipfs: %v", err)
}
log.Fatal(http.ListenAndServe("127.0.0.1:8080", http.FileServer(fs)))
}After you build your executable, append your zip file to it like so:
cat webassets.zip >>myserverbinary
zip -A myserverbinary
That's it!
For the more general case of supplying a zip file normally, you can pass any *zip.Reader and create a ZipFileSystem instance from that.
package main
import (
"archive/zip"
"log"
"net/http"
"os"
"gerace.dev/zipfs"
)
func main() {
f, err := os.Open("webassets.zip")
if err != nil {
log.Fatalf("Error opening webassets.zip: %v", err)
}
fi, err := f.Stat()
if err != nil {
log.Fatalf("Error getting file information about webassets.zip: %v", err)
}
zr, err := zip.NewReader(f, fi.Size())
if err != nil {
log.Fatalf("Error reading zip data from webassets.zip: %v", err)
}
fs, err := zipfs.NewZipFileSystem(zr)
if err != nil {
log.Fatalf("Error creating zip filesystem: %v", err)
}
log.Fatal(http.ListenAndServe("127.0.0.1:8080", http.FileServer(fs)))
}Options can be specified during instantiation, like so:
package main
import (
"log"
"net/http"
"gerace.dev/zipfs"
)
func main() {
fs, err := zipfs.NewEmbeddedZipFileSystem(zipfs.ServeIndexForMissing())
if err != nil {
log.Fatalf("Error setting up zipfs: %v", err)
}
log.Fatal(http.ListenAndServe("127.0.0.1:8080", http.FileServer(fs)))
}This option is useful for hosting single-page applications that mutate the browser history state locally. When specified, requests for
files that aren't in the zip archive will receive the content of index.html at the root of the zip archive, if it exists.