NOTE: this package is still under active development and has not yet reached version 1.0.
The assetserver package provides a file server designed for static assets.
The simplest way to use it is as a replacement for http.FileServer
:
assets := assetserver.New(assetFS)
where assetFS
is an embed.FS
or comes from os.DirFS
. Then the main handler
or muxer routes all requests with a prefix (say, /static/
) to assetSrv
. For
example:
mux := http.NewServeMux()
mux.Handle("/static/", http.StripPrefix("/static/", assets))
// ... set up remaining routes ...
Asset files that are served this way have two caching-related headers in the response:
ETag
contains a hash of the file contentsCache-Control
is set topublic, max-age=60
(1 minute)
This means that clients might cache the files for up to a minute before
revalidating. Revalidation with If-None-Match
is based on the file hash and
the server will always be able to respond with 304 Not Modified
unless the
actual file content has changed.
By writing a bit more code, assetserver can be used to implement a near-optimal
caching strategy. Asset files may be referred to by tagged names: instead of,
for example, style.css
, the client might request style.EI7Zfw9kFp.css
. The
tag (EI7Zfw9kFp
) is the same file hash in the ETag
header. When the file
changes, the tag changes. When a tagged file is served, the Cache-Control
header is
public, max-age=31536000, immutable
This means that files will generally be cached for as long as possible because the content corresponding to a tagged name never changes.
Tagged asset names are constructed with (*assetserver.Server).Tag
. This method
inserts a tag into a filename before the first dot.
Continuing our example from above, one might expose the Tag
method as a
template function:
funcs := template.FuncMap{
"tag": assets.Tag,
}
tmpl, err := template.New("").Funcs(funcs).Parse(tmplText)
...
Then the HTML template might contain:
<link rel="stylesheet" href="/static/{{tag "css/style.css"}}">
<script src="/static/{{tag "js/main.js"}}"></script>
The caching strategies described above are a significant advantage that
assetserver has over the standard library's file server when it comes to static
assets. Using http.FileServer
for these types of files can result in both
under- and over-caching:
- When files are served from disk, the only caching hint the client gets is in
the form of the
Last-Modified
header. The details depend on the particular heuristics of each client, but typically in this case clients cache the file for some fraction of the time since last modification. This means that if the file mtimes are changed on deploy, many clients may immediately re-request the full contents. It also means that if it has been a long time since the mtimes changed, the clients might cache the assets for a significant length of time and not notice that the server has updated the assets in the meantime. - When files are served from an
embed.FS
, there is no mtime, noLast-Modified
header, and clients generally won't cache the files at all.
Compared with http.FileServer
, assetserver also leaks less internal server
information and this also makes it better-suited for serving static assets:
- It does not serve directory listings (it returns
404 Not Found
for directories) - It does not serve
index.html
redirects - Internal errors (such as permission errors) are not exposed as HTTP errors; any internal error other than "not found" becomes a 500.
When running a server for local development, having assets be cached for even a
minute is generally undesirable. For this use case, a "no-cache" Server
can be
constructed using NewNoCache
. A Server
created this way serves files with
Cache-Control: no-cache
so that the browser will revalidate the assets on
every page load.
Additionally, tagged names can be undesirable in development environments
because these names interfere with developer tools that reload assets (such as
CSS files) when they change. Therefore, for a no-cache server, the Tag
method
returns the untagged path.
The assetserver package is designed specifically to serve static web assets that rarely or never change. Internally, it caches a bit of information about each file that it has ever served. This means that there are a few usages to be avoided:
- The server is not appropriate for serving a very large number of different files (say, millions)
- The internal cache never shrinks, so if new files are being created over time, memory usage will grow without bound even if old files are deleted.
- The internal cache uses {mtime, size} as a proxy to determine whether a file has changed (and therefore whether we need to recompute the hash). If a file changes without altering the mtime or size, or if a file is altered non-atomically, incorrect caching information will be served.