go 60 lines · 7 steps

Serving cached static assets in Gin

Wrapping an http.FileSystem and a Gin middleware to attach long-lived Cache-Control headers to static assets.

Explained by highlit
1package server
2 
3import (
4 "net/http"
5 "path/filepath"
6 "strings"
7 
8 "github.com/gin-gonic/gin"
9)
10 
11type cachedFileSystem struct {
12 fs http.FileSystem
13 maxAge string
14 immutable bool
15}
16 
17type cachedFile struct {
18 http.File
19}
20 
21func (c cachedFileSystem) Open(name string) (http.File, error) {
22 f, err := c.fs.Open(name)
23 if err != nil {
24 return nil, err
25 }
26 return cachedFile{f}, nil
27}
28 
29func newCachedFS(root string, maxAge string, immutable bool) cachedFileSystem {
30 return cachedFileSystem{
31 fs: http.Dir(root),
32 maxAge: maxAge,
33 immutable: immutable,
34 }
35}
36 
37func (c cachedFileSystem) cacheControl() string {
38 value := "public, max-age=" + c.maxAge
39 if c.immutable {
40 value += ", immutable"
41 }
42 return value
43}
44 
45func RegisterStatic(r *gin.Engine) {
46 assets := newCachedFS("./public/assets", "31536000", true)
47 
48 r.Use(func(ctx *gin.Context) {
49 if strings.HasPrefix(ctx.Request.URL.Path, "/assets/") {
50 ext := filepath.Ext(ctx.Request.URL.Path)
51 if ext != "" {
52 ctx.Header("Cache-Control", assets.cacheControl())
53 ctx.Header("Vary", "Accept-Encoding")
54 }
55 }
56 ctx.Next()
57 })
58 
59 r.StaticFS("/assets", assets)
60}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Wrapping http.FileSystem lets you customize file serving without reimplementing the whole handler.
  2. 2Immutable, year-long Cache-Control values are safe when asset URLs are content-fingerprinted.
  3. 3Gin middleware can inspect the request path and set response headers before the static handler runs.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Serving cached static assets in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code