forked from tangled.org/core
Monorepo for Tangled

appview,knotserver: use ETag based caching for blobs

this avoids stale raw content from being sent to clients.

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

oppi.li e83aa757 d7cc415c

verified
Changed files
+34 -9
appview
repo
knotserver
+22 -2
appview/repo/repo.go
··· 612 612 if !rp.config.Core.Dev { 613 613 protocol = "https" 614 614 } 615 + 615 616 blobURL := fmt.Sprintf("%s://%s/%s/%s/raw/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, filePath) 616 - resp, err := http.Get(blobURL) 617 + 618 + req, err := http.NewRequest("GET", blobURL, nil) 617 619 if err != nil { 618 - log.Println("failed to reach knotserver:", err) 620 + log.Println("failed to create request", err) 621 + return 622 + } 623 + 624 + // forward the If-None-Match header 625 + if clientETag := r.Header.Get("If-None-Match"); clientETag != "" { 626 + req.Header.Set("If-None-Match", clientETag) 627 + } 628 + 629 + client := &http.Client{} 630 + resp, err := client.Do(req) 631 + if err != nil { 632 + log.Println("failed to reach knotserver", err) 619 633 rp.pages.Error503(w) 620 634 return 621 635 } 622 636 defer resp.Body.Close() 637 + 638 + // forward 304 not modified 639 + if resp.StatusCode == http.StatusNotModified { 640 + w.WriteHeader(http.StatusNotModified) 641 + return 642 + } 623 643 624 644 if resp.StatusCode != http.StatusOK { 625 645 log.Printf("knotserver returned non-OK status for raw blob %s: %d", blobURL, resp.StatusCode)
+12 -7
knotserver/routes.go
··· 286 286 mimeType = "image/svg+xml" 287 287 } 288 288 289 + contentHash := sha256.Sum256(contents) 290 + eTag := fmt.Sprintf("\"%x\"", contentHash) 291 + 289 292 // allow image, video, and text/plain files to be served directly 290 293 switch { 291 - case strings.HasPrefix(mimeType, "image/"): 292 - // allowed 293 - case strings.HasPrefix(mimeType, "video/"): 294 - // allowed 294 + case strings.HasPrefix(mimeType, "image/"), strings.HasPrefix(mimeType, "video/"): 295 + if clientETag := r.Header.Get("If-None-Match"); clientETag == eTag { 296 + w.WriteHeader(http.StatusNotModified) 297 + return 298 + } 299 + w.Header().Set("ETag", eTag) 300 + 295 301 case strings.HasPrefix(mimeType, "text/plain"): 296 - // allowed 302 + w.Header().Set("Cache-Control", "public, no-cache") 303 + 297 304 default: 298 305 l.Error("attempted to serve disallowed file type", "mimetype", mimeType) 299 306 writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden) 300 307 return 301 308 } 302 309 303 - w.Header().Set("Cache-Control", "public, max-age=86400") // cache for 24 hours 304 - w.Header().Set("ETag", fmt.Sprintf("%x", sha256.Sum256(contents))) 305 310 w.Header().Set("Content-Type", mimeType) 306 311 w.Write(contents) 307 312 }