staticserve

package module
v1.1.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 28, 2026 License: MIT Imports: 13 Imported by: 5

README

build coverage goreport Docs

staticserve

staticserve is a cache-busting HTTP handler for static files. It supports GET and HEAD operations requesting the file with no encoding or gzip encoding.

Example

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/linkdata/staticserve"
)

func main() {
	mux := http.NewServeMux()
	uri, err := staticserve.Handle("app.js", []byte("console.log('hello');"), mux.Handle)
	if err != nil {
		log.Fatal(err)
	}

	// Use this URI in your HTML templates, e.g. <script src="{{.ScriptURI}}"></script>.
	fmt.Printf("script URI: %s\n", uri)

	log.Fatal(http.ListenAndServe(":8080", mux))
}

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

Examples

Constants

This section is empty.

Variables

View Source
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.

View Source
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.

View Source
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

func EnsurePrefixSlash(s string) string

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

func NormalizeGET(pattern string) string

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.

func WalkDir

func WalkDir(fsys fs.FS, root string, fn func(filename string, ss *StaticServe) (err error)) (err error)

WalkDir walks the file tree rooted at root, calling fn for each file in the tree with the filename having root trimmed (e.g. "root/dir/file.ext" becomes "dir/file.ext").

Types

type HandleFunc

type HandleFunc = func(uri string, handler http.Handler)

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL