|
|
|
@ -16,6 +16,7 @@
|
|
|
|
|
package macaron
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/base64"
|
|
|
|
|
"log"
|
|
|
|
|
"net/http"
|
|
|
|
|
"path"
|
|
|
|
@ -35,6 +36,9 @@ type StaticOptions struct {
|
|
|
|
|
// Expires defines which user-defined function to use for producing a HTTP Expires Header
|
|
|
|
|
// https://developers.google.com/speed/docs/insights/LeverageBrowserCaching
|
|
|
|
|
Expires func() string
|
|
|
|
|
// ETag defines if we should add an ETag header
|
|
|
|
|
// https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#validating-cached-responses-with-etags
|
|
|
|
|
ETag bool
|
|
|
|
|
// FileSystem is the interface for supporting any implmentation of file system.
|
|
|
|
|
FileSystem http.FileSystem
|
|
|
|
|
}
|
|
|
|
@ -172,10 +176,21 @@ func staticHandler(ctx *Context, log *log.Logger, opt StaticOptions) bool {
|
|
|
|
|
ctx.Resp.Header().Set("Expires", opt.Expires())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if opt.ETag {
|
|
|
|
|
tag := GenerateETag(string(fi.Size()), fi.Name(), fi.ModTime().UTC().Format(http.TimeFormat))
|
|
|
|
|
ctx.Resp.Header().Set("ETag", tag)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
http.ServeContent(ctx.Resp, ctx.Req.Request, file, fi.ModTime(), f)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GenerateETag generates an ETag based on size, filename and file modification time
|
|
|
|
|
func GenerateETag(fileSize, fileName, modTime string) string {
|
|
|
|
|
etag := fileSize + fileName + modTime
|
|
|
|
|
return base64.StdEncoding.EncodeToString([]byte(etag))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Static returns a middleware handler that serves static files in the given directory.
|
|
|
|
|
func Static(directory string, staticOpt ...StaticOptions) Handler {
|
|
|
|
|
opt := prepareStaticOptions(directory, staticOpt)
|
|
|
|
|