Search code examples
cachinggowebserver

Go webserver - don't cache files using timestamp


I'm running a webserver written in go on an embedded system. The timestamp of index.html may go backwards if someone has downgraded the firmware version. If index.html is older than the previous version, the server sends a http 304 response (not modified), and serves a cached version of the file.

The webserver code is using http.FileServer() and http.ListenAndServe().

The problem can easily reproduced by modifying the timestamp of index.html using the Posix command touch

touch -d"23:59" index.html

reloading the page, then

touch -d"23:58" index.html

reloading this time will give a 304 response on index.html.

Is there a way to prevent timestamp based caching?


Solution

  • Assuming your file server code is like the example in the docs:

    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("/static"))))
    

    You can write a handler that sets the appropriate cache headers to prevent this behaviour by stripping ETag headers and setting Cache-Control: no-cache, private, max-age=0 to prevent caching (both locally and in upstream proxies):

    var epoch = time.Unix(0, 0).Format(time.RFC1123)
    
    var noCacheHeaders = map[string]string{
        "Expires":         epoch,
        "Cache-Control":   "no-cache, private, max-age=0",
        "Pragma":          "no-cache",
        "X-Accel-Expires": "0",
    }
    
    var etagHeaders = []string{
        "ETag",
        "If-Modified-Since",
        "If-Match",
        "If-None-Match",
        "If-Range",
        "If-Unmodified-Since",
    }
    
    func NoCache(h http.Handler) http.Handler {
        fn := func(w http.ResponseWriter, r *http.Request) {
            // Delete any ETag headers that may have been set
            for _, v := range etagHeaders {
                if r.Header.Get(v) != "" {
                    r.Header.Del(v)
                }
            }
    
            // Set our NoCache headers
            for k, v := range noCacheHeaders {
                w.Header().Set(k, v)
            }
    
            h.ServeHTTP(w, r)
        }
    
        return http.HandlerFunc(fn)
    }
    

    Use it like so:

    http.Handle("/static/", NoCache(http.StripPrefix("/static/", http.FileServer(http.Dir("/static")))))
    

    Note: I originally wrote this at github.com/zenazn/goji/middleware, so you can also just import that, but it's a simple piece of code to write and I wanted to show a full example for posterity!