Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ETag header #15370

Merged
merged 14 commits into from
Apr 12, 2021
29 changes: 19 additions & 10 deletions modules/httpcache/httpcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,13 @@ func GetCacheControl() string {
// generateETag generates an ETag based on size, filename and file modification time
func generateETag(fi os.FileInfo) string {
etag := fmt.Sprint(fi.Size()) + fi.Name() + fi.ModTime().UTC().Format(http.TimeFormat)
return base64.StdEncoding.EncodeToString([]byte(etag))
return `"` + base64.StdEncoding.EncodeToString([]byte(etag)) + `"`
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
}

// HandleTimeCache handles time-based caching for a HTTP request
func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) {
w.Header().Set("Cache-Control", GetCacheControl())

ifModifiedSince := req.Header.Get("If-Modified-Since")
if ifModifiedSince != "" {
t, err := time.Parse(http.TimeFormat, ifModifiedSince)
Expand All @@ -40,20 +42,27 @@ func HandleTimeCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (
}
}

w.Header().Set("Cache-Control", GetCacheControl())
w.Header().Set("Last-Modified", fi.ModTime().Format(http.TimeFormat))
return false
}

// HandleEtagCache handles ETag-based caching for a HTTP request
func HandleEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) {
// HandleFileEtagCache handles ETag-based caching for a HTTP request
func HandleFileEtagCache(req *http.Request, w http.ResponseWriter, fi os.FileInfo) (handled bool) {
w.Header().Set("Cache-Control", GetCacheControl())

etag := generateETag(fi)
if req.Header.Get("If-None-Match") == etag {
w.WriteHeader(http.StatusNotModified)
return true
}
return HandleGenericETagCache(req, w, etag)
}

w.Header().Set("Cache-Control", GetCacheControl())
w.Header().Set("ETag", etag)
// HandleGenericETagCache handles ETag-based caching for a HTTP request.
// It returns true if the request was handled.
func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag string) bool {
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
if len(etag) > 0 {
w.Header().Set("Etag", etag)
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
if req.Header.Get("If-None-Match") == etag {
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
w.WriteHeader(http.StatusNotModified)
return true
}
}
return false
}
2 changes: 1 addition & 1 deletion modules/public/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ func (opts *Options) handle(w http.ResponseWriter, req *http.Request, opt *Optio
log.Println("[Static] Serving " + file)
}

if httpcache.HandleEtagCache(req, w, fi) {
if httpcache.HandleFileEtagCache(req, w, fi) {
return true
}

Expand Down
20 changes: 10 additions & 10 deletions routers/repo/attachment.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/storage"
Expand Down Expand Up @@ -124,21 +125,25 @@ func GetAttachment(ctx *context.Context) {
}
}

if err := attach.IncreaseDownloadCount(); err != nil {
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
ctx.ServerError("Update", err)
KN4CK3R marked this conversation as resolved.
Show resolved Hide resolved
return
}

if setting.Attachment.ServeDirect {
//If we have a signed url (S3, object storage), redirect to this directly.
u, err := storage.Attachments.URL(attach.RelativePath(), attach.Name)

if u != nil && err == nil {
if err := attach.IncreaseDownloadCount(); err != nil {
ctx.ServerError("Update", err)
return
}

ctx.Redirect(u.String())
return
}
}

if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+attach.UUID+`"`) {
return
}

//If we have matched and access to release or issue
fr, err := storage.Attachments.Open(attach.RelativePath())
if err != nil {
Expand All @@ -147,11 +152,6 @@ func GetAttachment(ctx *context.Context) {
}
defer fr.Close()

if err := attach.IncreaseDownloadCount(); err != nil {
ctx.ServerError("Update", err)
return
}

if err = ServeData(ctx, attach.Name, attach.Size, fr); err != nil {
ctx.ServerError("ServeData", err)
return
Expand Down
13 changes: 13 additions & 0 deletions routers/repo/download.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"code.gitea.io/gitea/modules/charset"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/lfs"
"code.gitea.io/gitea/modules/log"
)
Expand All @@ -31,6 +32,7 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader)
}

ctx.Resp.Header().Set("Cache-Control", "public,max-age=86400")

if size >= 0 {
ctx.Resp.Header().Set("Content-Length", fmt.Sprintf("%d", size))
} else {
Expand Down Expand Up @@ -71,6 +73,10 @@ func ServeData(ctx *context.Context, name string, size int64, reader io.Reader)

// ServeBlob download a git.Blob
func ServeBlob(ctx *context.Context, blob *git.Blob) error {
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) {
return nil
}

dataRc, err := blob.DataAsync()
if err != nil {
return err
Expand All @@ -86,6 +92,10 @@ func ServeBlob(ctx *context.Context, blob *git.Blob) error {

// ServeBlobOrLFS download a git.Blob redirecting to LFS if necessary
func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error {
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`) {
return nil
}

dataRc, err := blob.DataAsync()
if err != nil {
return err
Expand All @@ -102,6 +112,9 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob) error {
if meta == nil {
return ServeBlob(ctx, blob)
}
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) {
return nil
}
lfsDataRc, err := lfs.ReadMetaObject(meta.Pointer)
if err != nil {
return err
Expand Down