Refactor cache-control (#33861)

And fix #21391
pull/33860/head
wxiaoguang 16 hours ago committed by GitHub
parent 91610a987e
commit 3996518ed4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -4,40 +4,60 @@
package httpcache package httpcache
import ( import (
"io" "fmt"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
"time" "time"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
) )
type CacheControlOptions struct {
IsPublic bool
MaxAge time.Duration
NoTransform bool
}
// SetCacheControlInHeader sets suitable cache-control headers in the response // SetCacheControlInHeader sets suitable cache-control headers in the response
func SetCacheControlInHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) { func SetCacheControlInHeader(h http.Header, opts *CacheControlOptions) {
directives := make([]string, 0, 2+len(additionalDirectives)) directives := make([]string, 0, 4)
// "max-age=0 + must-revalidate" (aka "no-cache") is preferred instead of "no-store" // "max-age=0 + must-revalidate" (aka "no-cache") is preferred instead of "no-store"
// because browsers may restore some input fields after navigate-back / reload a page. // because browsers may restore some input fields after navigate-back / reload a page.
publicPrivate := util.Iif(opts.IsPublic, "public", "private")
if setting.IsProd { if setting.IsProd {
if maxAge == 0 { if opts.MaxAge == 0 {
directives = append(directives, "max-age=0", "private", "must-revalidate") directives = append(directives, "max-age=0", "private", "must-revalidate")
} else { } else {
directives = append(directives, "private", "max-age="+strconv.Itoa(int(maxAge.Seconds()))) directives = append(directives, publicPrivate, "max-age="+strconv.Itoa(int(opts.MaxAge.Seconds())))
} }
} else { } else {
directives = append(directives, "max-age=0", "private", "must-revalidate") // use dev-related controls, and remind users they are using non-prod setting.
directives = append(directives, "max-age=0", publicPrivate, "must-revalidate")
h.Set("X-Gitea-Debug", fmt.Sprintf("RUN_MODE=%v, MaxAge=%s", setting.RunMode, opts.MaxAge))
}
// to remind users they are using non-prod setting. if opts.NoTransform {
h.Set("X-Gitea-Debug", "RUN_MODE="+setting.RunMode) directives = append(directives, "no-transform")
} }
h.Set("Cache-Control", strings.Join(directives, ", "))
}
h.Set("Cache-Control", strings.Join(append(directives, additionalDirectives...), ", ")) func CacheControlForPublicStatic() *CacheControlOptions {
return &CacheControlOptions{
IsPublic: true,
MaxAge: setting.StaticCacheTime,
NoTransform: true,
}
} }
func ServeContentWithCacheControl(w http.ResponseWriter, req *http.Request, name string, modTime time.Time, content io.ReadSeeker) { func CacheControlForPrivateStatic() *CacheControlOptions {
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime) return &CacheControlOptions{
http.ServeContent(w, req, name, modTime, content) MaxAge: setting.StaticCacheTime,
NoTransform: true,
}
} }
// HandleGenericETagCache handles ETag-based caching for a HTTP request. // HandleGenericETagCache handles ETag-based caching for a HTTP request.
@ -50,7 +70,8 @@ func HandleGenericETagCache(req *http.Request, w http.ResponseWriter, etag strin
return true return true
} }
} }
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime) // not sure whether it is a public content, so just use "private" (old behavior)
SetCacheControlInHeader(w.Header(), CacheControlForPrivateStatic())
return false return false
} }
@ -95,6 +116,8 @@ func HandleGenericETagTimeCache(req *http.Request, w http.ResponseWriter, etag s
} }
} }
} }
SetCacheControlInHeader(w.Header(), setting.StaticCacheTime)
// not sure whether it is a public content, so just use "private" (old behavior)
SetCacheControlInHeader(w.Header(), CacheControlForPrivateStatic())
return false return false
} }

@ -33,6 +33,7 @@ type ServeHeaderOptions struct {
ContentLength *int64 ContentLength *int64
Disposition string // defaults to "attachment" Disposition string // defaults to "attachment"
Filename string Filename string
CacheIsPublic bool
CacheDuration time.Duration // defaults to 5 minutes CacheDuration time.Duration // defaults to 5 minutes
LastModified time.Time LastModified time.Time
} }
@ -72,11 +73,11 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
header.Set("Access-Control-Expose-Headers", "Content-Disposition") header.Set("Access-Control-Expose-Headers", "Content-Disposition")
} }
duration := opts.CacheDuration httpcache.SetCacheControlInHeader(header, &httpcache.CacheControlOptions{
if duration == 0 { IsPublic: opts.CacheIsPublic,
duration = 5 * time.Minute MaxAge: opts.CacheDuration,
} NoTransform: true,
httpcache.SetCacheControlInHeader(header, duration) })
if !opts.LastModified.IsZero() { if !opts.LastModified.IsZero() {
// http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat // http.TimeFormat required a UTC time, refer to https://pkg.go.dev/net/http#TimeFormat
@ -85,19 +86,15 @@ func ServeSetHeaders(w http.ResponseWriter, opts *ServeHeaderOptions) {
} }
// ServeData download file from io.Reader // ServeData download file from io.Reader
func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, filePath string, mineBuf []byte) { func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, mineBuf []byte, opts *ServeHeaderOptions) {
// do not set "Content-Length", because the length could only be set by callers, and it needs to support range requests // do not set "Content-Length", because the length could only be set by callers, and it needs to support range requests
opts := &ServeHeaderOptions{
Filename: path.Base(filePath),
}
sniffedType := typesniffer.DetectContentType(mineBuf) sniffedType := typesniffer.DetectContentType(mineBuf)
// the "render" parameter came from year 2016: 638dd24c, it doesn't have clear meaning, so I think it could be removed later // the "render" parameter came from year 2016: 638dd24c, it doesn't have clear meaning, so I think it could be removed later
isPlain := sniffedType.IsText() || r.FormValue("render") != "" isPlain := sniffedType.IsText() || r.FormValue("render") != ""
if setting.MimeTypeMap.Enabled { if setting.MimeTypeMap.Enabled {
fileExtension := strings.ToLower(filepath.Ext(filePath)) fileExtension := strings.ToLower(filepath.Ext(opts.Filename))
opts.ContentType = setting.MimeTypeMap.Map[fileExtension] opts.ContentType = setting.MimeTypeMap.Map[fileExtension]
} }
@ -114,7 +111,7 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, filePath stri
if isPlain { if isPlain {
charset, err := charsetModule.DetectEncoding(mineBuf) charset, err := charsetModule.DetectEncoding(mineBuf)
if err != nil { if err != nil {
log.Error("Detect raw file %s charset failed: %v, using by default utf-8", filePath, err) log.Error("Detect raw file %s charset failed: %v, using by default utf-8", opts.Filename, err)
charset = "utf-8" charset = "utf-8"
} }
opts.ContentTypeCharset = strings.ToLower(charset) opts.ContentTypeCharset = strings.ToLower(charset)
@ -142,7 +139,7 @@ func setServeHeadersByFile(r *http.Request, w http.ResponseWriter, filePath stri
const mimeDetectionBufferLen = 1024 const mimeDetectionBufferLen = 1024
func ServeContentByReader(r *http.Request, w http.ResponseWriter, filePath string, size int64, reader io.Reader) { func ServeContentByReader(r *http.Request, w http.ResponseWriter, size int64, reader io.Reader, opts *ServeHeaderOptions) {
buf := make([]byte, mimeDetectionBufferLen) buf := make([]byte, mimeDetectionBufferLen)
n, err := util.ReadAtMost(reader, buf) n, err := util.ReadAtMost(reader, buf)
if err != nil { if err != nil {
@ -152,7 +149,7 @@ func ServeContentByReader(r *http.Request, w http.ResponseWriter, filePath strin
if n >= 0 { if n >= 0 {
buf = buf[:n] buf = buf[:n]
} }
setServeHeadersByFile(r, w, filePath, buf) setServeHeadersByFile(r, w, buf, opts)
// reset the reader to the beginning // reset the reader to the beginning
reader = io.MultiReader(bytes.NewReader(buf), reader) reader = io.MultiReader(bytes.NewReader(buf), reader)
@ -215,7 +212,7 @@ func ServeContentByReader(r *http.Request, w http.ResponseWriter, filePath strin
_, _ = io.CopyN(w, reader, partialLength) // just like http.ServeContent, not necessary to handle the error _, _ = io.CopyN(w, reader, partialLength) // just like http.ServeContent, not necessary to handle the error
} }
func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, filePath string, modTime *time.Time, reader io.ReadSeeker) { func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, modTime *time.Time, reader io.ReadSeeker, opts *ServeHeaderOptions) {
buf := make([]byte, mimeDetectionBufferLen) buf := make([]byte, mimeDetectionBufferLen)
n, err := util.ReadAtMost(reader, buf) n, err := util.ReadAtMost(reader, buf)
if err != nil { if err != nil {
@ -229,9 +226,9 @@ func ServeContentByReadSeeker(r *http.Request, w http.ResponseWriter, filePath s
if n >= 0 { if n >= 0 {
buf = buf[:n] buf = buf[:n]
} }
setServeHeadersByFile(r, w, filePath, buf) setServeHeadersByFile(r, w, buf, opts)
if modTime == nil { if modTime == nil {
modTime = &time.Time{} modTime = &time.Time{}
} }
http.ServeContent(w, r, path.Base(filePath), *modTime, reader) http.ServeContent(w, r, opts.Filename, *modTime, reader)
} }

@ -27,7 +27,7 @@ func TestServeContentByReader(t *testing.T) {
} }
reader := strings.NewReader(data) reader := strings.NewReader(data)
w := httptest.NewRecorder() w := httptest.NewRecorder()
ServeContentByReader(r, w, "test", int64(len(data)), reader) ServeContentByReader(r, w, int64(len(data)), reader, &ServeHeaderOptions{})
assert.Equal(t, expectedStatusCode, w.Code) assert.Equal(t, expectedStatusCode, w.Code)
if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK { if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK {
assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length")) assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length"))
@ -76,7 +76,7 @@ func TestServeContentByReadSeeker(t *testing.T) {
defer seekReader.Close() defer seekReader.Close()
w := httptest.NewRecorder() w := httptest.NewRecorder()
ServeContentByReadSeeker(r, w, "test", nil, seekReader) ServeContentByReadSeeker(r, w, nil, seekReader, &ServeHeaderOptions{})
assert.Equal(t, expectedStatusCode, w.Code) assert.Equal(t, expectedStatusCode, w.Code)
if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK { if expectedStatusCode == http.StatusPartialContent || expectedStatusCode == http.StatusOK {
assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length")) assert.Equal(t, fmt.Sprint(len(expectedContent)), w.Header().Get("Content-Length"))

@ -86,17 +86,17 @@ func handleRequest(w http.ResponseWriter, req *http.Request, fs http.FileSystem,
return return
} }
serveContent(w, req, fi, fi.ModTime(), f) servePublicAsset(w, req, fi, fi.ModTime(), f)
} }
type GzipBytesProvider interface { type GzipBytesProvider interface {
GzipBytes() []byte GzipBytes() []byte
} }
// serveContent serve http content // servePublicAsset serve http content
func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) { func servePublicAsset(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modtime time.Time, content io.ReadSeeker) {
setWellKnownContentType(w, fi.Name()) setWellKnownContentType(w, fi.Name())
httpcache.SetCacheControlInHeader(w.Header(), httpcache.CacheControlForPublicStatic())
encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding")) encodings := parseAcceptEncoding(req.Header.Get("Accept-Encoding"))
if encodings.Contains("gzip") { if encodings.Contains("gzip") {
// try to provide gzip content directly from bindata (provided by vfsgen۰CompressedFileInfo) // try to provide gzip content directly from bindata (provided by vfsgen۰CompressedFileInfo)
@ -108,11 +108,10 @@ func serveContent(w http.ResponseWriter, req *http.Request, fi os.FileInfo, modt
w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Type", "application/octet-stream")
} }
w.Header().Set("Content-Encoding", "gzip") w.Header().Set("Content-Encoding", "gzip")
httpcache.ServeContentWithCacheControl(w, req, fi.Name(), modtime, rdGzip) http.ServeContent(w, req, fi.Name(), modtime, rdGzip)
return return
} }
} }
http.ServeContent(w, req, fi.Name(), modtime, content)
httpcache.ServeContentWithCacheControl(w, req, fi.Name(), modtime, content)
return return
} }

@ -83,7 +83,7 @@ func GetRawFile(ctx *context.APIContext) {
ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry))) ctx.RespHeader().Set(giteaObjectTypeHeader, string(files_service.GetObjectTypeFromTreeEntry(entry)))
if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil { if err := common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified); err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
} }
} }
@ -144,7 +144,7 @@ func GetRawFileOrLFS(ctx *context.APIContext) {
} }
// OK not cached - serve! // OK not cached - serve!
if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil { if err := common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified); err != nil {
ctx.APIErrorInternal(err) ctx.APIErrorInternal(err)
} }
return return

@ -32,7 +32,7 @@ func RenderPanicErrorPage(w http.ResponseWriter, req *http.Request, err any) {
routing.UpdatePanicError(req.Context(), err) routing.UpdatePanicError(req.Context(), err)
httpcache.SetCacheControlInHeader(w.Header(), 0, "no-transform") httpcache.SetCacheControlInHeader(w.Header(), &httpcache.CacheControlOptions{NoTransform: true})
w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) w.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
tmplCtx := context.TemplateContext{} tmplCtx := context.TemplateContext{}

@ -5,17 +5,21 @@ package common
import ( import (
"io" "io"
"path"
"time" "time"
repo_model "code.gitea.io/gitea/models/repo"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/httpcache" "code.gitea.io/gitea/modules/httpcache"
"code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/httplib"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/services/context" "code.gitea.io/gitea/services/context"
) )
// ServeBlob download a git.Blob // ServeBlob download a git.Blob
func ServeBlob(ctx *context.Base, filePath string, blob *git.Blob, lastModified *time.Time) error { func ServeBlob(ctx *context.Base, repo *repo_model.Repository, filePath string, blob *git.Blob, lastModified *time.Time) error {
if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) { if httpcache.HandleGenericETagTimeCache(ctx.Req, ctx.Resp, `"`+blob.ID.String()+`"`, lastModified) {
return nil return nil
} }
@ -30,14 +34,19 @@ func ServeBlob(ctx *context.Base, filePath string, blob *git.Blob, lastModified
} }
}() }()
httplib.ServeContentByReader(ctx.Req, ctx.Resp, filePath, blob.Size(), dataRc) _ = repo.LoadOwner(ctx)
httplib.ServeContentByReader(ctx.Req, ctx.Resp, blob.Size(), dataRc, &httplib.ServeHeaderOptions{
Filename: path.Base(filePath),
CacheIsPublic: !repo.IsPrivate && repo.Owner != nil && repo.Owner.Visibility == structs.VisibleTypePublic,
CacheDuration: setting.StaticCacheTime,
})
return nil return nil
} }
func ServeContentByReader(ctx *context.Base, filePath string, size int64, reader io.Reader) { func ServeContentByReader(ctx *context.Base, filePath string, size int64, reader io.Reader) {
httplib.ServeContentByReader(ctx.Req, ctx.Resp, filePath, size, reader) httplib.ServeContentByReader(ctx.Req, ctx.Resp, size, reader, &httplib.ServeHeaderOptions{Filename: path.Base(filePath)})
} }
func ServeContentByReadSeeker(ctx *context.Base, filePath string, modTime *time.Time, reader io.ReadSeeker) { func ServeContentByReadSeeker(ctx *context.Base, filePath string, modTime *time.Time, reader io.ReadSeeker) {
httplib.ServeContentByReadSeeker(ctx.Req, ctx.Resp, filePath, modTime, reader) httplib.ServeContentByReadSeeker(ctx.Req, ctx.Resp, modTime, reader, &httplib.ServeHeaderOptions{Filename: path.Base(filePath)})
} }

@ -19,12 +19,12 @@ import (
"code.gitea.io/gitea/modules/web/routing" "code.gitea.io/gitea/modules/web/routing"
) )
func storageHandler(storageSetting *setting.Storage, prefix string, objStore storage.ObjectStorage) http.HandlerFunc { func avatarStorageHandler(storageSetting *setting.Storage, prefix string, objStore storage.ObjectStorage) http.HandlerFunc {
prefix = strings.Trim(prefix, "/") prefix = strings.Trim(prefix, "/")
funcInfo := routing.GetFuncInfo(storageHandler, prefix) funcInfo := routing.GetFuncInfo(avatarStorageHandler, prefix)
if storageSetting.ServeDirect() { if storageSetting.ServeDirect() {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" && req.Method != "HEAD" { if req.Method != "GET" && req.Method != "HEAD" {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return return
@ -52,10 +52,10 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
} }
http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect) http.Redirect(w, req, u.String(), http.StatusTemporaryRedirect)
}) }
} }
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { return func(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" && req.Method != "HEAD" { if req.Method != "GET" && req.Method != "HEAD" {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
return return
@ -93,6 +93,8 @@ func storageHandler(storageSetting *setting.Storage, prefix string, objStore sto
return return
} }
defer fr.Close() defer fr.Close()
httpcache.ServeContentWithCacheControl(w, req, path.Base(rPath), fi.ModTime(), fr)
}) httpcache.SetCacheControlInHeader(w.Header(), httpcache.CacheControlForPublicStatic())
http.ServeContent(w, req, path.Base(rPath), fi.ModTime(), fr)
}
} }

@ -38,7 +38,7 @@ func RobotsTxt(w http.ResponseWriter, req *http.Request) {
if ok, _ := util.IsExist(robotsTxt); !ok { if ok, _ := util.IsExist(robotsTxt); !ok {
robotsTxt = util.FilePathJoinAbs(setting.CustomPath, "robots.txt") // the legacy "robots.txt" robotsTxt = util.FilePathJoinAbs(setting.CustomPath, "robots.txt") // the legacy "robots.txt"
} }
httpcache.SetCacheControlInHeader(w.Header(), setting.StaticCacheTime) httpcache.SetCacheControlInHeader(w.Header(), httpcache.CacheControlForPublicStatic())
http.ServeFile(w, req, robotsTxt) http.ServeFile(w, req, robotsTxt)
} }

@ -46,7 +46,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified *time.Tim
log.Error("ServeBlobOrLFS: Close: %v", err) log.Error("ServeBlobOrLFS: Close: %v", err)
} }
closed = true closed = true
return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified) return common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified)
} }
if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) { if httpcache.HandleGenericETagCache(ctx.Req, ctx.Resp, `"`+pointer.Oid+`"`) {
return nil return nil
@ -78,7 +78,7 @@ func ServeBlobOrLFS(ctx *context.Context, blob *git.Blob, lastModified *time.Tim
} }
closed = true closed = true
return common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified) return common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified)
} }
func getBlobForEntry(ctx *context.Context) (*git.Blob, *time.Time) { func getBlobForEntry(ctx *context.Context) (*git.Blob, *time.Time) {
@ -114,7 +114,7 @@ func SingleDownload(ctx *context.Context) {
return return
} }
if err := common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, lastModified); err != nil { if err := common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, lastModified); err != nil {
ctx.ServerError("ServeBlob", err) ctx.ServerError("ServeBlob", err)
} }
} }
@ -142,7 +142,7 @@ func DownloadByID(ctx *context.Context) {
} }
return return
} }
if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, blob, nil); err != nil { if err = common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, blob, nil); err != nil {
ctx.ServerError("ServeBlob", err) ctx.ServerError("ServeBlob", err)
} }
} }

@ -740,7 +740,7 @@ func WikiRaw(ctx *context.Context) {
} }
if entry != nil { if entry != nil {
if err = common.ServeBlob(ctx.Base, ctx.Repo.TreePath, entry.Blob(), nil); err != nil { if err = common.ServeBlob(ctx.Base, ctx.Repo.Repository, ctx.Repo.TreePath, entry.Blob(), nil); err != nil {
ctx.ServerError("ServeBlob", err) ctx.ServerError("ServeBlob", err)
} }
return return

@ -16,7 +16,7 @@ func cacheableRedirect(ctx *context.Context, location string) {
// here we should not use `setting.StaticCacheTime`, it is pretty long (default: 6 hours) // here we should not use `setting.StaticCacheTime`, it is pretty long (default: 6 hours)
// we must make sure the redirection cache time is short enough, otherwise a user won't see the updated avatar in 6 hours // we must make sure the redirection cache time is short enough, otherwise a user won't see the updated avatar in 6 hours
// it's OK to make the cache time short, it is only a redirection, and doesn't cost much to make a new request // it's OK to make the cache time short, it is only a redirection, and doesn't cost much to make a new request
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 5*time.Minute) httpcache.SetCacheControlInHeader(ctx.Resp.Header(), &httpcache.CacheControlOptions{MaxAge: 5 * time.Minute})
ctx.Redirect(location) ctx.Redirect(location)
} }

@ -233,8 +233,8 @@ func Routes() *web.Router {
routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler routes.Head("/", misc.DummyOK) // for health check - doesn't need to be passed through gzip handler
routes.Methods("GET, HEAD, OPTIONS", "/assets/*", optionsCorsHandler(), public.FileHandlerFunc()) routes.Methods("GET, HEAD, OPTIONS", "/assets/*", optionsCorsHandler(), public.FileHandlerFunc())
routes.Methods("GET, HEAD", "/avatars/*", storageHandler(setting.Avatar.Storage, "avatars", storage.Avatars)) routes.Methods("GET, HEAD", "/avatars/*", avatarStorageHandler(setting.Avatar.Storage, "avatars", storage.Avatars))
routes.Methods("GET, HEAD", "/repo-avatars/*", storageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars)) routes.Methods("GET, HEAD", "/repo-avatars/*", avatarStorageHandler(setting.RepoAvatar.Storage, "repo-avatars", storage.RepoAvatars))
routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png")) routes.Methods("GET, HEAD", "/apple-touch-icon.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
routes.Methods("GET, HEAD", "/apple-touch-icon-precomposed.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png")) routes.Methods("GET, HEAD", "/apple-touch-icon-precomposed.png", misc.StaticRedirect("/assets/img/apple-touch-icon.png"))
routes.Methods("GET, HEAD", "/favicon.ico", misc.StaticRedirect("/assets/img/favicon.png")) routes.Methods("GET, HEAD", "/favicon.ico", misc.StaticRedirect("/assets/img/favicon.png"))

@ -232,7 +232,7 @@ func APIContexter() func(http.Handler) http.Handler {
} }
} }
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform") httpcache.SetCacheControlInHeader(ctx.Resp.Header(), &httpcache.CacheControlOptions{NoTransform: true})
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
next.ServeHTTP(ctx.Resp, ctx.Req) next.ServeHTTP(ctx.Resp, ctx.Req)

@ -191,7 +191,7 @@ func Contexter() func(next http.Handler) http.Handler {
} }
} }
httpcache.SetCacheControlInHeader(ctx.Resp.Header(), 0, "no-transform") httpcache.SetCacheControlInHeader(ctx.Resp.Header(), &httpcache.CacheControlOptions{NoTransform: true})
ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions) ctx.Resp.Header().Set(`X-Frame-Options`, setting.CORSConfig.XFrameOptions)
ctx.Data["SystemConfig"] = setting.Config() ctx.Data["SystemConfig"] = setting.Config()

Loading…
Cancel
Save