tangled
alpha
login
or
join now
back
round
1
view raw
appview,knotserver: use ETag based caching for blobs
#511
merged
opened by
oppi.li
4 months ago
targeting
master
from
push-pwqwlvnsqtqr
this avoids stale raw content from being sent to clients.
Signed-off-by: oppiliappan
me@oppi.li
options
unified
split
Changed files
+34
-9
appview
repo
repo.go
knotserver
routes.go
+22
-2
appview/repo/repo.go
···
612
if !rp.config.Core.Dev {
613
protocol = "https"
614
}
0
615
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)
0
0
0
0
0
0
0
0
0
0
0
0
0
617
if err != nil {
618
-
log.Println("failed to reach knotserver:", err)
619
rp.pages.Error503(w)
620
return
621
}
622
defer resp.Body.Close()
623
0
0
0
0
0
0
624
if resp.StatusCode != http.StatusOK {
625
log.Printf("knotserver returned non-OK status for raw blob %s: %d", blobURL, resp.StatusCode)
626
w.WriteHeader(resp.StatusCode)
···
612
if !rp.config.Core.Dev {
613
protocol = "https"
614
}
615
+
616
blobURL := fmt.Sprintf("%s://%s/%s/%s/raw/%s/%s", protocol, f.Knot, f.OwnerDid(), f.Repo.Name, ref, filePath)
617
+
618
+
req, err := http.NewRequest("GET", blobURL, nil)
619
+
if err != nil {
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)
633
rp.pages.Error503(w)
634
return
635
}
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
+
}
643
+
644
if resp.StatusCode != http.StatusOK {
645
log.Printf("knotserver returned non-OK status for raw blob %s: %d", blobURL, resp.StatusCode)
646
w.WriteHeader(resp.StatusCode)
+12
-7
knotserver/routes.go
···
286
mimeType = "image/svg+xml"
287
}
288
0
0
0
289
// allow image, video, and text/plain files to be served directly
290
switch {
291
-
case strings.HasPrefix(mimeType, "image/"):
292
-
// allowed
293
-
case strings.HasPrefix(mimeType, "video/"):
294
-
// allowed
0
0
0
295
case strings.HasPrefix(mimeType, "text/plain"):
296
-
// allowed
0
297
default:
298
l.Error("attempted to serve disallowed file type", "mimetype", mimeType)
299
writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden)
300
return
301
}
302
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
w.Header().Set("Content-Type", mimeType)
306
w.Write(contents)
307
}
···
286
mimeType = "image/svg+xml"
287
}
288
289
+
contentHash := sha256.Sum256(contents)
290
+
eTag := fmt.Sprintf("\"%x\"", contentHash)
291
+
292
// allow image, video, and text/plain files to be served directly
293
switch {
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
+
301
case strings.HasPrefix(mimeType, "text/plain"):
302
+
w.Header().Set("Cache-Control", "public, no-cache")
303
+
304
default:
305
l.Error("attempted to serve disallowed file type", "mimetype", mimeType)
306
writeError(w, "only image, video, and text files can be accessed directly", http.StatusForbidden)
307
return
308
}
309
0
0
310
w.Header().Set("Content-Type", mimeType)
311
w.Write(contents)
312
}