【问题标题】:gorilla/mux golang cache static filesgorilla/mux golang 缓存静态文件
【发布时间】:2018-07-30 06:54:20
【问题描述】:

我有一个 go web 应用程序,它提供静态 HTML/JS/CSS 文件以及一些 API 端点。我注意到我的 HTML/JS/CSS 没有被缓存在浏览器上。例如,每次我重新加载页面时,它们都会被完全重新下载。

这是我需要设置的服务器端配置更改吗?如何使用 Go 和 Gorilla Mux 完成此任务?

我使用的是 Google App Engine,所以 Nginx 是不可能的。

这是我的 main.go 代码:

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)
}

编辑:这是@Topo 建议的解决方案:

   // 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)
    })
}

【问题讨论】:

    标签: go gorilla


    【解决方案1】:

    要告诉浏览器缓存您的文件,您需要告诉它多长时间,否则用户将永远不会看到文件的较新版本。

    为此,只需在处理函数中设置Cache-Control 标头:

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

    使用 Etag 标头让浏览器知道何时有新版本的文件也是一个好主意。这样您就可以将文件缓存很长时间,并在新内容可用时立即将其提供给用户:

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

    每次文件更改时,您都需要使用不同的 etag 值。浏览器存储它,并且仅当 etag 与存储的值不同时才重新加载文件。您可以为 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)
    

    您可以阅读 Cache-ControlEtag heders 的 mozilla 文档。

    如果您想避免为静态文件编写自己的处理程序函数,而是继续使用http.FileServer,则需要在编写响应之前将其包装在设置标头的处理程序中。 Here 是一篇关于包装 Web 处理程序的博文。

    【讨论】:

    • 太棒了,谢谢。就我而言,我使用的是 webpack,它会在每次构建后创建一个新的 js 文件,例如 /static/js/app.3df99dd891ed2460b6bc.js,所以我认为我不需要 etag 部分,但很高兴知道
    • @user1527312 只要js文件的名称发生变化,没有它应该没问题。如果您认为它是正确的,请不要忘记接受答案。
    • 应该是 if err == nil 而不是 !=