aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMorgan Bazalgette <git@howl.moe>2018-02-03 23:37:05 +0100
committerLauris BH <lauris@nix.lv>2018-02-04 00:37:05 +0200
commit17655cdf1b409521262d5d54eb19884d307c47ce (patch)
treeb06d15367afc6bd4f915c0e8b9272241bb3f4a3c
parent77f8bad2fb7a6a4ab57b398cb89e6889f76ffe8a (diff)
downloadgitea-17655cdf1b409521262d5d54eb19884d307c47ce.tar.gz
gitea-17655cdf1b409521262d5d54eb19884d307c47ce.zip
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
-rw-r--r--models/user.go2
-rw-r--r--modules/public/dynamic.go7
-rw-r--r--modules/public/public.go138
-rw-r--r--modules/public/static.go23
-rw-r--r--routers/routes/routes.go19
5 files changed, 155 insertions, 34 deletions
diff --git a/models/user.go b/models/user.go
index bf28683285..ecfe3bca0f 100644
--- a/models/user.go
+++ b/models/user.go
@@ -145,7 +145,7 @@ func (u *User) BeforeUpdate() {
if len(u.AvatarEmail) == 0 {
u.AvatarEmail = u.Email
}
- if len(u.AvatarEmail) > 0 {
+ if len(u.AvatarEmail) > 0 && u.Avatar == "" {
u.Avatar = base.HashEmail(u.AvatarEmail)
}
}
diff --git a/modules/public/dynamic.go b/modules/public/dynamic.go
index c196d67baa..282db44970 100644
--- a/modules/public/dynamic.go
+++ b/modules/public/dynamic.go
@@ -12,10 +12,5 @@ import (
// Static implements the macaron static handler for serving assets.
func Static(opts *Options) macaron.Handler {
- return macaron.Static(
- opts.Directory,
- macaron.StaticOptions{
- SkipLogging: opts.SkipLogging,
- },
- )
+ return opts.staticHandler(opts.Directory)
}
diff --git a/modules/public/public.go b/modules/public/public.go
index 6f28ebc032..f03f8fcc15 100644
--- a/modules/public/public.go
+++ b/modules/public/public.go
@@ -5,7 +5,13 @@
package public
import (
+ "encoding/base64"
+ "log"
+ "net/http"
"path"
+ "path/filepath"
+ "strings"
+ "time"
"code.gitea.io/gitea/modules/setting"
"gopkg.in/macaron.v1"
@@ -19,15 +25,135 @@ import (
// Options represents the available options to configure the macaron handler.
type Options struct {
Directory string
+ IndexFile string
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.
func Custom(opts *Options) macaron.Handler {
- return macaron.Static(
- path.Join(setting.CustomPath, "public"),
- macaron.StaticOptions{
- SkipLogging: opts.SkipLogging,
- },
- )
+ return opts.staticHandler(path.Join(setting.CustomPath, "public"))
+}
+
+// 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))
}
diff --git a/modules/public/static.go b/modules/public/static.go
index f68400d329..10e32dbd10 100644
--- a/modules/public/static.go
+++ b/modules/public/static.go
@@ -13,17 +13,14 @@ import (
// Static implements the macaron static handler for serving assets.
func Static(opts *Options) macaron.Handler {
- return macaron.Static(
- opts.Directory,
- macaron.StaticOptions{
- SkipLogging: opts.SkipLogging,
- FileSystem: bindata.Static(bindata.Options{
- Asset: Asset,
- AssetDir: AssetDir,
- AssetInfo: AssetInfo,
- AssetNames: AssetNames,
- Prefix: "",
- }),
- },
- )
+ opts.FileSystem = bindata.Static(bindata.Options{
+ Asset: Asset,
+ AssetDir: AssetDir,
+ AssetInfo: AssetInfo,
+ AssetNames: AssetNames,
+ Prefix: "",
+ })
+ // we don't need to pass the directory, because the directory var is only
+ // used when in the options there is no FileSystem.
+ return opts.staticHandler("")
}
diff --git a/routers/routes/routes.go b/routers/routes/routes.go
index e51bfb946a..1d95bb4c76 100644
--- a/routers/routes/routes.go
+++ b/routers/routes/routes.go
@@ -7,6 +7,7 @@ package routes
import (
"os"
"path"
+ "time"
"code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/auth"
@@ -53,21 +54,23 @@ func NewMacaron() *macaron.Macaron {
}
m.Use(public.Custom(
&public.Options{
- SkipLogging: setting.DisableRouterLog,
+ SkipLogging: setting.DisableRouterLog,
+ ExpiresAfter: time.Hour * 6,
},
))
m.Use(public.Static(
&public.Options{
- Directory: path.Join(setting.StaticRootPath, "public"),
- SkipLogging: setting.DisableRouterLog,
+ Directory: path.Join(setting.StaticRootPath, "public"),
+ SkipLogging: setting.DisableRouterLog,
+ ExpiresAfter: time.Hour * 6,
},
))
- m.Use(macaron.Static(
+ m.Use(public.StaticHandler(
setting.AvatarUploadPath,
- macaron.StaticOptions{
- Prefix: "avatars",
- SkipLogging: setting.DisableRouterLog,
- ETag: true,
+ &public.Options{
+ Prefix: "avatars",
+ SkipLogging: setting.DisableRouterLog,
+ ExpiresAfter: time.Hour * 6,
},
))