gorilla/mux golang cache static files
Asked Answered
C

1

8

I have a go web app which serves static HTML/JS/CSS files plus has some API endpoints. I noticed that my HTML/JS/CSS are not being cached on the browser. E.g., every time I reload a page, they are fully re-downloaded.

Is this a server side configuration change that I need to set? How can I accomplish this with Go and Gorilla Mux?

I'm using Google App Engine, so Nginx is not a possibility.

Here's my main.go code:

package main

import (
    "associations"
    "html/template"
    "net/http"
    "log"
    "io/ioutil"

    "github.com/gorilla/mux"
    "github.com/rs/cors"
    "google.golang.org/appengine"
    "google.golang.org/appengine/mail"
)

var index = template.Must(template.ParseFiles(
    "dist/index.html",
))

func init() {
    r := mux.NewRouter()
    r.HandleFunc("/", homeHandler)  
    r.HandleFunc("/api/{tenant}/certificates", associations.GetCertificate).Methods("GET")
    r.HandleFunc("/api/{tenant}/requests", associations.PostRequest).Methods("POST")

    // handle static files
    r.PathPrefix("/static/").Handler(
        http.StripPrefix("/static/", http.FileServer(http.Dir("dist/static/"))))

    r.NotFoundHandler = http.HandlerFunc(homeHandler) // work around for SPA serving index.html

    handler := cors.Default().Handler(r)
    http.Handle("/", handler)
}

EDIT: Here's the solution with @Topo's suggestion:

   // handle static files
        r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", 
CacheControlWrapper(http.FileServer(http.Dir("dist/static/")))))

    ....

func CacheControlWrapper(h http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Cache-Control", "max-age=2592000") // 30 days
        h.ServeHTTP(w, r)
    })
}
Chondro answered 20/2, 2018 at 0:46 Comment(0)
K
12

To tell the browser to cache your files, you need to tell it for how long, if not the user would never see newer versions of the file.

To do this just set the Cache-Control header inside your handler function:

w.Header().Set("Cache-Control", "max-age=3600")

It's also a good idea to use the Etag header to let the browsers know when there is a new version of the file. That way you can cache the files for a really long time, and still serve the new content to the users as soon as it's available:

etag := "some unique value"
w.Header().Set("Etag", etag)

You need to use a different etag value every time the file changes. The browser stores it and only reloads the file when the etag is different to the stored value. You can use something like the file name + modified date for the etag:

var modTime time.Time
fi, err := fh.Stat()
if err == nil {
    modTime = fi.ModTime()
} else {
    modTime = time.Now()
}
etag := "\"" + file + modTime.String() + "\""
w.Header().Set("Etag", etag)

You can read the mozilla docs for the Cache-Control and Etag heders.

If you want to avoid writing your own handler function for static files and instead keep using http.FileServer, you will need to wrap it in a handler that sets the headers before writing the response. Here is a blog post on wrapping web handlers.

Kuhl answered 20/2, 2018 at 1:53 Comment(3)
awesome, thank you. In my case I'm using webpack which creates a new js file like /static/js/app.3df99dd891ed2460b6bc.js after every build, so I don't think the etag piece is needed for me, but good to knowChondro
@Chondro As long as the name of the js file changes you should be ok without it. Don't forget to accept the answer if you think it it the correct one.Kuhl
it should be if err == nil instead of !=Kautz

© 2022 - 2024 — McMap. All rights reserved.