Documentation
¶
Overview ¶
Package staticserve serves single static assets under cache-busted file names. Each asset is held gzip-compressed in memory and served either as gzip (when the client advertises Accept-Encoding: gzip) or decompressed on the fly otherwise. The cache-busting name is derived from a hash of the uncompressed content, so it stays stable across gzip encoder differences.
Index ¶
- Variables
- func EnsurePrefixSlash(s string) string
- func Handle(fpath string, data []byte, handleFn HandleFunc) (uri string, err error)
- func HandleFS(fsys fs.FS, handleFn HandleFunc, root string, filepaths ...string) (uris []string, err error)
- func NormalizeGET(pattern string) string
- func WalkDir(fsys fs.FS, root string, fn func(filename string, ss *StaticServe) (err error)) (err error)
- type HandleFunc
- type StaticServe
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var HeaderAllow = []string{http.MethodGet + ", " + http.MethodHead}
HeaderAllow is the Allow header value sent with 405 Method Not Allowed responses to requests that use methods other than GET or HEAD. Like HeaderCacheControl, reassign it before serving rather than mutating it in place.
var HeaderCacheControl = []string{"public, max-age=31536000, s-maxage=31536000, immutable"}
HeaderCacheControl is the Cache-Control header value sent with successful responses. Its default marks the asset as immutable for one year, which is safe because the served file name is cache-busted via a content hash.
To customize it, reassign it before serving any request. It is shared by reference across all responses and read without synchronization, so mutating it (or its elements) while requests are in flight is a data race.
var HeaderVary = []string{"Accept-Encoding"}
HeaderVary is the Vary header value sent with successful responses. Like HeaderCacheControl, reassign it before serving rather than mutating it in place.
Functions ¶
func EnsurePrefixSlash ¶
EnsurePrefixSlash returns s with a leading slash.
func Handle ¶
func Handle(fpath string, data []byte, handleFn HandleFunc) (uri string, err error)
Handle creates a new StaticServe for the fpath that returns the data given. Returns the URI of the resource. The pattern passed to handleFn is method-aware: bare path patterns are normalized to GET via NormalizeGET.
Example ¶
package main
import (
"fmt"
"net/http"
"net/http/httptest"
"strings"
"github.com/linkdata/staticserve"
)
func main() {
mux := http.NewServeMux()
uri, err := staticserve.Handle("app.js", []byte("console.log('hello');"), mux.Handle)
if err != nil {
panic(err)
}
req := httptest.NewRequest(http.MethodGet, uri, nil)
req.Header.Set("Accept-Encoding", "gzip")
rr := httptest.NewRecorder()
mux.ServeHTTP(rr, req)
res := rr.Result()
fmt.Println(res.StatusCode == http.StatusOK)
fmt.Println(res.Header.Get("Content-Encoding"))
fmt.Println(strings.HasPrefix(uri, "/app."))
fmt.Println(strings.HasSuffix(uri, ".js"))
}
Output: true gzip true true
func HandleFS ¶
func HandleFS(fsys fs.FS, handleFn HandleFunc, root string, filepaths ...string) (uris []string, err error)
HandleFS creates StaticServe handlers for the filepaths given. Returns the URI(s) of the resources. If an error occurs, the URI of the failed resource will be the empty string.
HandleFS attempts every filepath and registers each resource as it succeeds. A non-nil error can therefore be returned alongside routes registered for any successful filepaths.
func NormalizeGET ¶
NormalizeGET returns a method-aware ServeMux pattern.
If pattern already has a method prefix, it is returned unchanged. Otherwise GET is prepended and the path is made absolute.
Types ¶
type HandleFunc ¶
HandleFunc matches the signature of http.ServeMux.Handle().
type StaticServe ¶
type StaticServe struct {
Name string // the cache-busting file name, e.g. "static/filename.1234567.js" or "/static/filename.1234567.js"
ContentType string // Content-Type of the file, e.g. "application/javascript"
Size int64 // uncompressed length of the asset in bytes
Gz []byte // gzipped data, will be unpacked as needed
}
StaticServe is an http.Handler that serves a single asset under a cache-busted file name. The asset is held gzip-compressed in memory and served either as gzip (when the client advertises Accept-Encoding: gzip) or decompressed on the fly otherwise.
Instances are safe for concurrent use after construction and are intended to be created via New, NewFS, Must, or MustNewFS so that all fields are populated consistently. The exported fields should be treated as read-only after construction; mutating them while requests are in flight is a data race.
func Must ¶
func Must(filename string, data []byte) (ss *StaticServe)
Must calls New and panics on error.
func MustNewFS ¶
func MustNewFS(fsys fs.FS, root string, fpaths ...string) (ssl []*StaticServe)
MustNewFS calls NewFS for each fpath relative to root and returns the resulting StaticServe values in order. It panics on the first error.
func New ¶
func New(filename string, data []byte) (ss *StaticServe, err error)
New returns a StaticServe that serves the given data with a filename like 'filename.12345678.ext'. The filename must be a valid slash-separated relative path, or the same path with a leading slash, excluding "." and "/". The filename must have the suffix ".gz" if the data is GZip compressed. The ".gz" suffix will not be part of the filename presented in this case.
func NewFS ¶
func NewFS(fsys fs.FS, root, fpath string) (ss *StaticServe, err error)
NewFS reads the file at fpath from fsys and then calls New.
func (*StaticServe) ServeHTTP ¶
func (ss *StaticServe) ServeHTTP(w http.ResponseWriter, r *http.Request)
ServeHTTP serves the asset for GET and HEAD requests.
When the client advertises Accept-Encoding: gzip the gzip-compressed bytes are served verbatim; otherwise the asset is decompressed on the fly. Content-Length always reflects the size of the body that would be returned by an equivalent GET, so HEAD responses are usable for size discovery.
Methods other than GET and HEAD receive 405 Method Not Allowed with an Allow header. When the asset must be decompressed (the client does not accept gzip) and the stored gzip stream cannot be opened, 500 Internal Server Error is returned with no body; this should not happen for instances created via New. Clients that accept gzip receive the stored bytes verbatim and are never served a 500.