appview,knotserver: use ETag based caching for blobs #511

merged
opened by oppi.li targeting master from push-pwqwlvnsqtqr

this avoids stale raw content from being sent to clients.

Signed-off-by: oppiliappan me@oppi.li

Changed files
+34 -9
appview
repo
knotserver
+22 -2
appview/repo/repo.go
···
if !rp.config.Core.Dev {
protocol = "https"
}
+
blobURL := fmt.Sprintf("%s://%s/%s/%s/raw/%s/%s", protocol, f.Knot, f.OwnerDid(), f.RepoName, ref, filePath)
-
resp, err := http.Get(blobURL)
+
+
req, err := http.NewRequest("GET", blobURL, nil)
+
if err != nil {
+
log.Println("failed to create request", err)
+
return
+
}
+
+
// forward the If-None-Match header
+
if clientETag := r.Header.Get("If-None-Match"); clientETag != "" {
+
req.Header.Set("If-None-Match", clientETag)
+
}
+
+
client := &http.Client{}
+
resp, err := client.Do(req)
if err != nil {
-
log.Println("failed to reach knotserver:", err)
+
log.Println("failed to reach knotserver", err)
rp.pages.Error503(w)
return
}
defer resp.Body.Close()
+
// forward 304 not modified
+
if resp.StatusCode == http.StatusNotModified {
+
w.WriteHeader(http.StatusNotModified)
+
return
+
}
+
if resp.StatusCode != http.StatusOK {
log.Printf("knotserver returned non-OK status for raw blob %s: %d", blobURL, resp.StatusCode)
w.WriteHeader(resp.StatusCode)
+12 -7
knotserver/routes.go
···
mimeType = "image/svg+xml"
}
+
contentHash := sha256.Sum256(contents)
+
eTag := fmt.Sprintf("\"%x\"", contentHash)
+
// allow image, video, and text/plain files to be served directly
switch {
-
case strings.HasPrefix(mimeType, "image/"):
-
// allowed
-
case strings.HasPrefix(mimeType, "video/"):
-
// allowed
+
case strings.HasPrefix(mimeType, "image/"), strings.HasPrefix(mimeType, "video/"):
+
if clientETag := r.Header.Get("If-None-Match"); clientETag == eTag {
+
w.WriteHeader(http.StatusNotModified)
+
return
+
}
+
w.Header().Set("ETag", eTag)
+
case strings.HasPrefix(mimeType, "text/plain"):
-
// allowed
+
w.Header().Set("Cache-Control", "public, no-cache")
+
default:
l.Error("attempted to serve disallowed file type", "mimetype", mimeType)
writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden)
return
}
-
w.Header().Set("Cache-Control", "public, max-age=86400") // cache for 24 hours
-
w.Header().Set("ETag", fmt.Sprintf("%x", sha256.Sum256(contents)))
w.Header().Set("Content-Type", mimeType)
w.Write(contents)
}