Skip to content

Commit 9f5ddca

Browse files
zeripath6543
andauthored
Set the LastModified header for raw files (#18356)
Although the use of LastModified dates for caching of git objects should be discouraged (as it is not native to git - and there are a LOT of ways this could be incorrect) - LastModified dates can be a helpful somewhat more human way of caching for simple cases. This PR adds this header and handles the If-Modified-Since header to the /raw/ routes. Fix #18354 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: 6543 <6543@obermui.de>
1 parent e435283 commit 9f5ddca

File tree

5 files changed

+133
-29
lines changed

5 files changed

+133
-29
lines changed

modules/httpcache/httpcache.go

+37-2
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,23 @@ func generateETag(fi os.FileInfo) string {
3737

3838
// HandleTimeCache handles time-based caching for a HTTP request
3939
func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) {
40+
return HandleGenericTimeCache(req, w, fi.ModTime())
41+
}
42+
43+
// HandleGenericTimeCache handles time-based caching for a HTTP request
44+
func HandleGenericTimeCache(req *http.Request, w http.ResponseWriter, lastModified time.Time) (handled bool) {
4045
AddCacheControlToHeader(w.Header(), setting.StaticCacheTime)
4146

4247
ifModifiedSince := req.Header.Get("If-Modified-Since")
4348
if ifModifiedSince != "" {
4449
t, err := time.Parse(http.TimeFormat, ifModifiedSince)
45-
if err == nil && fi.ModTime().Unix() <= t.Unix() {
50+
if err == nil && lastModified.Unix() <= t.Unix() {
4651
w.WriteHeader(http.StatusNotModified)
4752
return true
4853
}
4954
}
5055

51-
w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
56+
w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat))
5257
return false
5358
}
5459

@@ -85,3 +90,33 @@ func checkIfNoneMatchIsValid(req *http.Request, etag string) bool {
8590
}
8691
return false
8792
}
93+
94+
// HandleGenericETagTimeCache handles ETag-based caching with Last-Modified caching for a HTTP request.
95+
// It returns true if the request was handled.
96+
func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag string, lastModified time.Time) (handled bool) {
97+
if len(etag) > 0 {
98+
w.Header().Set("Etag", etag)
99+
}
100+
if !lastModified.IsZero() {
101+
w.Header().Set("Last-Modified", lastModified.Format(http.TimeFormat))
102+
}
103+
104+
if len(etag) > 0 {
105+
if checkIfNoneMatchIsValid(req, etag) {
106+
w.WriteHeader(http.StatusNotModified)
107+
return true
108+
}
109+
}
110+
if !lastModified.IsZero() {
111+
ifModifiedSince := req.Header.Get("If-Modified-Since")
112+
if ifModifiedSince != "" {
113+
t, err := time.Parse(http.TimeFormat, ifModifiedSince)
114+
if err == nil && lastModified.Unix() <= t.Unix() {
115+
w.WriteHeader(http.StatusNotModified)
116+
return true
117+
}
118+
}
119+
}
120+
AddCacheControlToHeader(w.Header(), setting.StaticCacheTime)
121+
return false
122+
}

routers/api/v1/repo/file.go

+39-4
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,16 @@ import (
99
"encoding/base64"
1010
"fmt"
1111
"net/http"
12+
"path"
1213
"time"
1314

1415
"code.gitea.io/gitea/models"
1516
repo_model "code.gitea.io/gitea/models/repo"
1617
"code.gitea.io/gitea/models/unit"
18+
"code.gitea.io/gitea/modules/cache"
1719
"code.gitea.io/gitea/modules/context"
1820
"code.gitea.io/gitea/modules/git"
21+
"code.gitea.io/gitea/modules/setting"
1922
api "code.gitea.io/gitea/modules/structs"
2023
"code.gitea.io/gitea/modules/web"
2124
"code.gitea.io/gitea/routers/common"
@@ -62,18 +65,50 @@ func GetRawFile(ctx *context.APIContext) {
6265
return
6366
}
6467

65-
blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreePath)
68+
blob, lastModified := getBlobForEntry(ctx)
69+
if ctx.Written() {
70+
return
71+
}
72+
73+
if err := common.ServeBlob(ctx.Context, blob, lastModified); err != nil {
74+
ctx.Error(http.StatusInternalServerError, "ServeBlob", err)
75+
}
76+
}
77+
78+
func getBlobForEntry(ctx *context.APIContext) (blob *git.Blob, lastModified time.Time) {
79+
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
6680
if err != nil {
6781
if git.IsErrNotExist(err) {
6882
ctx.NotFound()
6983
} else {
70-
ctx.Error(http.StatusInternalServerError, "GetBlobByPath", err)
84+
ctx.Error(http.StatusInternalServerError, "GetTreeEntryByPath", err)
7185
}
7286
return
7387
}
74-
if err = common.ServeBlob(ctx.Context, blob); err != nil {
75-
ctx.Error(http.StatusInternalServerError, "ServeBlob", err)
88+
89+
if entry.IsDir() || entry.IsSubModule() {
90+
ctx.NotFound("getBlobForEntry", nil)
91+
return
7692
}
93+
94+
var c *git.LastCommitCache
95+
if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount {
96+
c = git.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, setting.LastCommitCacheTTLSeconds, cache.GetCache())
97+
}
98+
99+
info, _, err := git.Entries([]*git.TreeEntry{entry}).GetCommitsInfo(ctx, ctx.Repo.Commit, path.Dir("/" + ctx.Repo.TreePath)[1:], c)
100+
if err != nil {
101+
ctx.Error(http.StatusInternalServerError, "GetCommitsInfo", err)
102+
return
103+
}
104+
105+
if len(info) == 1 {
106+
// Not Modified
107+
lastModified = info[0].Commit.Committer.When
108+
}
109+
blob = entry.Blob()
110+
111+
return
77112
}
78113

79114
// GetArchive get archive of a repository

routers/common/repo.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"path"
1111
"path/filepath"
1212
"strings"
13+
"time"
1314

1415
"code.gitea.io/gitea/modules/charset"
1516
"code.gitea.io/gitea/modules/context"
@@ -22,8 +23,8 @@ import (
2223
)
2324

2425
// ServeBlob download a git.Blob
25-
func ServeBlob(ctx *context.Context, blob *git.Blob) error {
26-
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) {
26+
func ServeBlob(ctx *context.Context, blob *git.Blob, lastModified time.Time) error {
27+
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
2728
return nil
2829
}
2930

routers/web/repo/download.go

+52-20
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,11 @@
66
package repo
77

88
import (
9+
"path"
10+
"time"
11+
912
"code.gitea.io/gitea/models"
13+
"code.gitea.io/gitea/modules/cache"
1014
"code.gitea.io/gitea/modules/context"
1115
"code.gitea.io/gitea/modules/git"
1216
"code.gitea.io/gitea/modules/httpcache"
@@ -18,8 +22,8 @@ import (
1822
)
1923

2024
// ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary
21-
func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error {
22-
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) {
25+
func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified time.Time) error {
26+
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
2327
return nil
2428
}
2529

@@ -45,7 +49,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error {
4549
log.Error("ServeBlobOrLFS: Close: %v", err)
4650
}
4751
closed = true
48-
return common.ServeBlob(ctx, blob)
52+
return common.ServeBlob(ctx, blob, lastModified)
4953
}
5054
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) {
5155
return nil
@@ -76,37 +80,65 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error {
7680
}
7781
closed = true
7882

79-
return common.ServeBlob(ctx, blob)
83+
return common.ServeBlob(ctx, blob, lastModified)
8084
}
8185

82-
// SingleDownload download a file by repos path
83-
func SingleDownload(ctx *context.Context) {
84-
blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreePath)
86+
func getBlobForEntry(ctx *context.Context) (blob *git.Blob, lastModified time.Time) {
87+
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath)
8588
if err != nil {
8689
if git.IsErrNotExist(err) {
87-
ctx.NotFound("GetBlobByPath", nil)
90+
ctx.NotFound("GetTreeEntryByPath", err)
8891
} else {
89-
ctx.ServerError("GetBlobByPath", err)
92+
ctx.ServerError("GetTreeEntryByPath", err)
9093
}
9194
return
9295
}
93-
if err = common.ServeBlob(ctx, blob); err != nil {
96+
97+
if entry.IsDir() || entry.IsSubModule() {
98+
ctx.NotFound("getBlobForEntry", nil)
99+
return
100+
}
101+
102+
var c *git.LastCommitCache
103+
if setting.CacheService.LastCommit.Enabled && ctx.Repo.CommitsCount >= setting.CacheService.LastCommit.CommitsCount {
104+
c = git.NewLastCommitCache(ctx.Repo.Repository.FullName(), ctx.Repo.GitRepo, setting.LastCommitCacheTTLSeconds, cache.GetCache())
105+
}
106+
107+
info, _, err := git.Entries([]*git.TreeEntry{entry}).GetCommitsInfo(ctx, ctx.Repo.Commit, path.Dir("/" + ctx.Repo.TreePath)[1:], c)
108+
if err != nil {
109+
ctx.ServerError("GetCommitsInfo", err)
110+
return
111+
}
112+
113+
if len(info) == 1 {
114+
// Not Modified
115+
lastModified = info[0].Commit.Committer.When
116+
}
117+
blob = entry.Blob()
118+
119+
return
120+
}
121+
122+
// SingleDownload download a file by repos path
123+
func SingleDownload(ctx *context.Context) {
124+
blob, lastModified := getBlobForEntry(ctx)
125+
if blob == nil {
126+
return
127+
}
128+
129+
if err := common.ServeBlob(ctx, blob, lastModified); err != nil {
94130
ctx.ServerError("ServeBlob", err)
95131
}
96132
}
97133

98134
// SingleDownloadOrLFS download a file by repos path redirecting to LFS if necessary
99135
func SingleDownloadOrLFS(ctx *context.Context) {
100-
blob, err := ctx.Repo.Commit.GetBlobByPath(ctx.Repo.TreePath)
101-
if err != nil {
102-
if git.IsErrNotExist(err) {
103-
ctx.NotFound("GetBlobByPath", nil)
104-
} else {
105-
ctx.ServerError("GetBlobByPath", err)
106-
}
136+
blob, lastModified := getBlobForEntry(ctx)
137+
if blob == nil {
107138
return
108139
}
109-
if err = ServeBlobOrLFS(ctx, blob); err != nil {
140+
141+
if err := ServeBlobOrLFS(ctx, blob, lastModified); err != nil {
110142
ctx.ServerError("ServeBlobOrLFS", err)
111143
}
112144
}
@@ -122,7 +154,7 @@ func DownloadByID(ctx *context.Context) {
122154
}
123155
return
124156
}
125-
if err = common.ServeBlob(ctx, blob); err != nil {
157+
if err = common.ServeBlob(ctx, blob, time.Time{}); err != nil {
126158
ctx.ServerError("ServeBlob", err)
127159
}
128160
}
@@ -138,7 +170,7 @@ func DownloadByIDOrLFS(ctx *context.Context) {
138170
}
139171
return
140172
}
141-
if err = ServeBlobOrLFS(ctx, blob); err != nil {
173+
if err = ServeBlobOrLFS(ctx, blob, time.Time{}); err != nil {
142174
ctx.ServerError("ServeBlob", err)
143175
}
144176
}

routers/web/repo/wiki.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"net/url"
1414
"path/filepath"
1515
"strings"
16+
"time"
1617

1718
"code.gitea.io/gitea/models"
1819
"code.gitea.io/gitea/models/unit"
@@ -627,7 +628,7 @@ func WikiRaw(ctx *context.Context) {
627628
}
628629

629630
if entry != nil {
630-
if err = common.ServeBlob(ctx, entry.Blob()); err != nil {
631+
if err = common.ServeBlob(ctx, entry.Blob(), time.Time{}); err != nil {
631632
ctx.ServerError("ServeBlob", err)
632633
}
633634
return

0 commit comments

Comments
 (0)