mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 16:40:24 +08:00 
			
		
		
		
	Enable caching on assets and avatars (#3376)
* Enable caching on assets and avatars Fixes #3323 * Only set avatar in user BeforeUpdate when there is no avatar set * add error checking after stat * gofmt * Change cache time for avatars to an hour
This commit is contained in:
		
				
					committed by
					
						
						Lauris BH
					
				
			
			
				
	
			
			
			
						parent
						
							77f8bad2fb
						
					
				
				
					commit
					17655cdf1b
				
			@@ -145,7 +145,7 @@ func (u *User) BeforeUpdate() {
 | 
				
			|||||||
		if len(u.AvatarEmail) == 0 {
 | 
							if len(u.AvatarEmail) == 0 {
 | 
				
			||||||
			u.AvatarEmail = u.Email
 | 
								u.AvatarEmail = u.Email
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if len(u.AvatarEmail) > 0 {
 | 
							if len(u.AvatarEmail) > 0 && u.Avatar == "" {
 | 
				
			||||||
			u.Avatar = base.HashEmail(u.AvatarEmail)
 | 
								u.Avatar = base.HashEmail(u.AvatarEmail)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,10 +12,5 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Static implements the macaron static handler for serving assets.
 | 
					// Static implements the macaron static handler for serving assets.
 | 
				
			||||||
func Static(opts *Options) macaron.Handler {
 | 
					func Static(opts *Options) macaron.Handler {
 | 
				
			||||||
	return macaron.Static(
 | 
						return opts.staticHandler(opts.Directory)
 | 
				
			||||||
		opts.Directory,
 | 
					 | 
				
			||||||
		macaron.StaticOptions{
 | 
					 | 
				
			||||||
			SkipLogging: opts.SkipLogging,
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,13 @@
 | 
				
			|||||||
package public
 | 
					package public
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/base64"
 | 
				
			||||||
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"gopkg.in/macaron.v1"
 | 
						"gopkg.in/macaron.v1"
 | 
				
			||||||
@@ -19,15 +25,135 @@ import (
 | 
				
			|||||||
// Options represents the available options to configure the macaron handler.
 | 
					// Options represents the available options to configure the macaron handler.
 | 
				
			||||||
type Options struct {
 | 
					type Options struct {
 | 
				
			||||||
	Directory   string
 | 
						Directory   string
 | 
				
			||||||
 | 
						IndexFile   string
 | 
				
			||||||
	SkipLogging bool
 | 
						SkipLogging bool
 | 
				
			||||||
 | 
						// if set to true, will enable caching. Expires header will also be set to
 | 
				
			||||||
 | 
						// expire after the defined time.
 | 
				
			||||||
 | 
						ExpiresAfter time.Duration
 | 
				
			||||||
 | 
						FileSystem   http.FileSystem
 | 
				
			||||||
 | 
						Prefix       string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Custom implements the macaron static handler for serving custom assets.
 | 
					// Custom implements the macaron static handler for serving custom assets.
 | 
				
			||||||
func Custom(opts *Options) macaron.Handler {
 | 
					func Custom(opts *Options) macaron.Handler {
 | 
				
			||||||
	return macaron.Static(
 | 
						return opts.staticHandler(path.Join(setting.CustomPath, "public"))
 | 
				
			||||||
		path.Join(setting.CustomPath, "public"),
 | 
					}
 | 
				
			||||||
		macaron.StaticOptions{
 | 
					
 | 
				
			||||||
			SkipLogging: opts.SkipLogging,
 | 
					// staticFileSystem implements http.FileSystem interface.
 | 
				
			||||||
		},
 | 
					type staticFileSystem struct {
 | 
				
			||||||
	)
 | 
						dir *http.Dir
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func newStaticFileSystem(directory string) staticFileSystem {
 | 
				
			||||||
 | 
						if !filepath.IsAbs(directory) {
 | 
				
			||||||
 | 
							directory = filepath.Join(macaron.Root, directory)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						dir := http.Dir(directory)
 | 
				
			||||||
 | 
						return staticFileSystem{&dir}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (fs staticFileSystem) Open(name string) (http.File, error) {
 | 
				
			||||||
 | 
						return fs.dir.Open(name)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// StaticHandler sets up a new middleware for serving static files in the
 | 
				
			||||||
 | 
					func StaticHandler(dir string, opts *Options) macaron.Handler {
 | 
				
			||||||
 | 
						return opts.staticHandler(dir)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (opts *Options) staticHandler(dir string) macaron.Handler {
 | 
				
			||||||
 | 
						// Defaults
 | 
				
			||||||
 | 
						if len(opts.IndexFile) == 0 {
 | 
				
			||||||
 | 
							opts.IndexFile = "index.html"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Normalize the prefix if provided
 | 
				
			||||||
 | 
						if opts.Prefix != "" {
 | 
				
			||||||
 | 
							// Ensure we have a leading '/'
 | 
				
			||||||
 | 
							if opts.Prefix[0] != '/' {
 | 
				
			||||||
 | 
								opts.Prefix = "/" + opts.Prefix
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// Remove any trailing '/'
 | 
				
			||||||
 | 
							opts.Prefix = strings.TrimRight(opts.Prefix, "/")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if opts.FileSystem == nil {
 | 
				
			||||||
 | 
							opts.FileSystem = newStaticFileSystem(dir)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return func(ctx *macaron.Context, log *log.Logger) {
 | 
				
			||||||
 | 
							opts.handle(ctx, log, opts)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (opts *Options) handle(ctx *macaron.Context, log *log.Logger, opt *Options) bool {
 | 
				
			||||||
 | 
						if ctx.Req.Method != "GET" && ctx.Req.Method != "HEAD" {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						file := ctx.Req.URL.Path
 | 
				
			||||||
 | 
						// if we have a prefix, filter requests by stripping the prefix
 | 
				
			||||||
 | 
						if opt.Prefix != "" {
 | 
				
			||||||
 | 
							if !strings.HasPrefix(file, opt.Prefix) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							file = file[len(opt.Prefix):]
 | 
				
			||||||
 | 
							if file != "" && file[0] != '/' {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						f, err := opt.FileSystem.Open(file)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return false
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						defer f.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fi, err := f.Stat()
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Printf("[Static] %q exists, but fails to open: %v", file, err)
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Try to serve index file
 | 
				
			||||||
 | 
						if fi.IsDir() {
 | 
				
			||||||
 | 
							// Redirect if missing trailing slash.
 | 
				
			||||||
 | 
							if !strings.HasSuffix(ctx.Req.URL.Path, "/") {
 | 
				
			||||||
 | 
								http.Redirect(ctx.Resp, ctx.Req.Request, ctx.Req.URL.Path+"/", http.StatusFound)
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							f, err = opt.FileSystem.Open(file)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return false // Discard error.
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							defer f.Close()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							fi, err = f.Stat()
 | 
				
			||||||
 | 
							if err != nil || fi.IsDir() {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if !opt.SkipLogging {
 | 
				
			||||||
 | 
							log.Println("[Static] Serving " + file)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Add an Expires header to the static content
 | 
				
			||||||
 | 
						if opt.ExpiresAfter > 0 {
 | 
				
			||||||
 | 
							ctx.Resp.Header().Set("Expires", time.Now().Add(opt.ExpiresAfter).UTC().Format(http.TimeFormat))
 | 
				
			||||||
 | 
							tag := GenerateETag(string(fi.Size()), fi.Name(), fi.ModTime().UTC().Format(http.TimeFormat))
 | 
				
			||||||
 | 
							ctx.Resp.Header().Set("ETag", tag)
 | 
				
			||||||
 | 
							if ctx.Req.Header.Get("If-None-Match") == tag {
 | 
				
			||||||
 | 
								ctx.Resp.WriteHeader(304)
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						http.ServeContent(ctx.Resp, ctx.Req.Request, file, fi.ModTime(), f)
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GenerateETag generates an ETag based on size, filename and file modification time
 | 
				
			||||||
 | 
					func GenerateETag(fileSize, fileName, modTime string) string {
 | 
				
			||||||
 | 
						etag := fileSize + fileName + modTime
 | 
				
			||||||
 | 
						return base64.StdEncoding.EncodeToString([]byte(etag))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -13,17 +13,14 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Static implements the macaron static handler for serving assets.
 | 
					// Static implements the macaron static handler for serving assets.
 | 
				
			||||||
func Static(opts *Options) macaron.Handler {
 | 
					func Static(opts *Options) macaron.Handler {
 | 
				
			||||||
	return macaron.Static(
 | 
						opts.FileSystem = bindata.Static(bindata.Options{
 | 
				
			||||||
		opts.Directory,
 | 
							Asset:      Asset,
 | 
				
			||||||
		macaron.StaticOptions{
 | 
							AssetDir:   AssetDir,
 | 
				
			||||||
			SkipLogging: opts.SkipLogging,
 | 
							AssetInfo:  AssetInfo,
 | 
				
			||||||
			FileSystem: bindata.Static(bindata.Options{
 | 
							AssetNames: AssetNames,
 | 
				
			||||||
				Asset:      Asset,
 | 
							Prefix:     "",
 | 
				
			||||||
				AssetDir:   AssetDir,
 | 
						})
 | 
				
			||||||
				AssetInfo:  AssetInfo,
 | 
						// we don't need to pass the directory, because the directory var is only
 | 
				
			||||||
				AssetNames: AssetNames,
 | 
						// used when in the options there is no FileSystem.
 | 
				
			||||||
				Prefix:     "",
 | 
						return opts.staticHandler("")
 | 
				
			||||||
			}),
 | 
					 | 
				
			||||||
		},
 | 
					 | 
				
			||||||
	)
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,6 +7,7 @@ package routes
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/auth"
 | 
						"code.gitea.io/gitea/modules/auth"
 | 
				
			||||||
@@ -53,21 +54,23 @@ func NewMacaron() *macaron.Macaron {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	m.Use(public.Custom(
 | 
						m.Use(public.Custom(
 | 
				
			||||||
		&public.Options{
 | 
							&public.Options{
 | 
				
			||||||
			SkipLogging: setting.DisableRouterLog,
 | 
								SkipLogging:  setting.DisableRouterLog,
 | 
				
			||||||
 | 
								ExpiresAfter: time.Hour * 6,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	))
 | 
						))
 | 
				
			||||||
	m.Use(public.Static(
 | 
						m.Use(public.Static(
 | 
				
			||||||
		&public.Options{
 | 
							&public.Options{
 | 
				
			||||||
			Directory:   path.Join(setting.StaticRootPath, "public"),
 | 
								Directory:    path.Join(setting.StaticRootPath, "public"),
 | 
				
			||||||
			SkipLogging: setting.DisableRouterLog,
 | 
								SkipLogging:  setting.DisableRouterLog,
 | 
				
			||||||
 | 
								ExpiresAfter: time.Hour * 6,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	))
 | 
						))
 | 
				
			||||||
	m.Use(macaron.Static(
 | 
						m.Use(public.StaticHandler(
 | 
				
			||||||
		setting.AvatarUploadPath,
 | 
							setting.AvatarUploadPath,
 | 
				
			||||||
		macaron.StaticOptions{
 | 
							&public.Options{
 | 
				
			||||||
			Prefix:      "avatars",
 | 
								Prefix:       "avatars",
 | 
				
			||||||
			SkipLogging: setting.DisableRouterLog,
 | 
								SkipLogging:  setting.DisableRouterLog,
 | 
				
			||||||
			ETag:        true,
 | 
								ExpiresAfter: time.Hour * 6,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	))
 | 
						))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user