diff --git a/knotmirror/xrpc/git_format_patch.go b/knotmirror/xrpc/git_format_patch.go index a63a5eee1..42888d0ef 100644 --- a/knotmirror/xrpc/git_format_patch.go +++ b/knotmirror/xrpc/git_format_patch.go @@ -46,8 +46,7 @@ func (x *Xrpc) FormatPatch(w http.ResponseWriter, r *http.Request) { cmd.Stderr = stderr w.Header().Set("Content-Type", "text/plain; charset=utf-8") - // a format-patch range is immutable by commit sha, so edges may cache it - w.Header().Set("Cache-Control", "public, max-age=3600, s-maxage=86400, immutable") + setCacheControl(w, from, to) cmd.Stdout = w if err := cmd.Run(); err != nil { l.Error("running format-patch", "err", err, "stderr", stderr.String()) diff --git a/knotmirror/xrpc/git_get_archive.go b/knotmirror/xrpc/git_get_archive.go index 4c85b3c5a..75548d6bc 100644 --- a/knotmirror/xrpc/git_get_archive.go +++ b/knotmirror/xrpc/git_get_archive.go @@ -65,6 +65,7 @@ func (x *Xrpc) GetArchive(w http.ResponseWriter, r *http.Request) { hash := gitutil.RevFromHash(commit.Hash) served := params.WithRev(params.Rev.Or(hash)).Serve(gitutil.RepoName(mirrored.Name)).WithRev(hash) + setCacheControl(w, params.Rev.String()) served.SetHeaders(w.Header()) w.Header().Set("Link", gitutil.ImmutableLink(fmt.Sprintf("%s/xrpc/%s?%s", x.cfg.BaseUrl(), tangled.GitTempGetArchiveNSID, served.Query(repo.String()).Encode(), diff --git a/knotmirror/xrpc/git_get_blob.go b/knotmirror/xrpc/git_get_blob.go index 9339a7ebe..a799470a9 100644 --- a/knotmirror/xrpc/git_get_blob.go +++ b/knotmirror/xrpc/git_get_blob.go @@ -60,6 +60,8 @@ func (x *Xrpc) GetBlob(w http.ResponseWriter, r *http.Request) { } defer reader.Close() + setCacheControl(w, ref) + // default to octet-stream for large blobs if size > 1024*1024 { // 1MiB w.Header().Set("Content-Length", strconv.FormatInt(size, 10)) @@ -106,7 +108,6 @@ func (x *Xrpc) GetBlob(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", mimeType) case strings.HasPrefix(mimeType, "text/") || isTextualMimeType(mimeType): - w.Header().Set("Cache-Control", "public, no-cache") // serve all text content as text/plain w.Header().Set("Content-Type", "text/plain; charset=utf-8") diff --git a/knotmirror/xrpc/git_get_entry.go b/knotmirror/xrpc/git_get_entry.go index e8fbdf14c..4f9b9e193 100644 --- a/knotmirror/xrpc/git_get_entry.go +++ b/knotmirror/xrpc/git_get_entry.go @@ -114,6 +114,8 @@ func (x *Xrpc) GetEntry(w http.ResponseWriter, r *http.Request) { } } + setCacheControl(w, r.URL.Query().Get("ref")) + writeJson(w, http.StatusOK, tangled.GitTempGetEntry_Output{ Name: entry.Name, Mode: entry.Mode.String(), diff --git a/knotmirror/xrpc/git_get_merge_base.go b/knotmirror/xrpc/git_get_merge_base.go index f59722fa6..a7a1b7308 100644 --- a/knotmirror/xrpc/git_get_merge_base.go +++ b/knotmirror/xrpc/git_get_merge_base.go @@ -44,6 +44,8 @@ func (x *Xrpc) GetMergeBase(w http.ResponseWriter, r *http.Request) { return } + setCacheControl(w, base, head) + cmd := exec.Command("git", "-C", repoPath, "merge-base", head, base) out, err := cmd.Output() if err != nil { diff --git a/knotmirror/xrpc/git_get_tree.go b/knotmirror/xrpc/git_get_tree.go index c12b6f3bf..d94ec3432 100644 --- a/knotmirror/xrpc/git_get_tree.go +++ b/knotmirror/xrpc/git_get_tree.go @@ -50,6 +50,7 @@ func (x *Xrpc) GetTree(w http.ResponseWriter, r *http.Request) { writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "failed to get tree"}) return } + setCacheControl(w, ref) writeJson(w, http.StatusOK, out) } diff --git a/knotmirror/xrpc/git_list_languages.go b/knotmirror/xrpc/git_list_languages.go index c3dce79a0..00eb4b016 100644 --- a/knotmirror/xrpc/git_list_languages.go +++ b/knotmirror/xrpc/git_list_languages.go @@ -65,5 +65,7 @@ func (x *Xrpc) ListLanguages(w http.ResponseWriter, r *http.Request) { }) } + setCacheControl(w, r.URL.Query().Get("ref")) + writeJson(w, http.StatusOK, &out) } diff --git a/knotmirror/xrpc/xrpc.go b/knotmirror/xrpc/xrpc.go index 6c42f023d..15ca53377 100644 --- a/knotmirror/xrpc/xrpc.go +++ b/knotmirror/xrpc/xrpc.go @@ -15,8 +15,10 @@ import ( "github.com/go-chi/chi/v5" "github.com/hashicorp/golang-lru/v2/expirable" "github.com/redis/go-redis/v9" + "github.com/samber/lo" "golang.org/x/sync/singleflight" "tangled.org/core/api/tangled" + "tangled.org/core/gitutil" "tangled.org/core/idresolver" "tangled.org/core/knotmirror/config" "tangled.org/core/knotmirror/knotstream" @@ -187,3 +189,10 @@ func writeErr(w http.ResponseWriter, err error) error { } return writeJson(w, http.StatusInternalServerError, atclient.ErrorBody{Name: "InternalServerError", Message: "internal server error"}) } +func setCacheControl(w http.ResponseWriter, refs ...string) { + if len(refs) > 0 && lo.EveryBy(refs, func(ref string) bool { return gitutil.Rev(ref).IsObjectID() }) { + w.Header().Set("Cache-Control", "public, max-age=31536000, immutable") + } else { + w.Header().Set("Cache-Control", "public, no-cache") + } +}