1
0
mirror of https://github.com/astaxie/beego.git synced 2024-11-26 00:01:29 +00:00

Merge pull request #3984 from jianzhiyao/develop

fix bug:static can not real hit cache & memory leak
This commit is contained in:
Ming Deng 2020-06-06 13:10:58 +08:00 committed by GitHub
commit b8efb3ef45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 86 additions and 23 deletions

View File

@ -52,6 +52,8 @@ func oldMap() M {
m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex m["BConfig.WebConfig.DirectoryIndex"] = BConfig.WebConfig.DirectoryIndex
m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir m["BConfig.WebConfig.StaticDir"] = BConfig.WebConfig.StaticDir
m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip m["BConfig.WebConfig.StaticExtensionsToGzip"] = BConfig.WebConfig.StaticExtensionsToGzip
m["BConfig.WebConfig.StaticCacheFileSize"] = BConfig.WebConfig.StaticCacheFileSize
m["BConfig.WebConfig.StaticCacheFileNum"] = BConfig.WebConfig.StaticCacheFileNum
m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft m["BConfig.WebConfig.TemplateLeft"] = BConfig.WebConfig.TemplateLeft
m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight m["BConfig.WebConfig.TemplateRight"] = BConfig.WebConfig.TemplateRight
m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath m["BConfig.WebConfig.ViewsPath"] = BConfig.WebConfig.ViewsPath

View File

@ -81,6 +81,8 @@ type WebConfig struct {
DirectoryIndex bool DirectoryIndex bool
StaticDir map[string]string StaticDir map[string]string
StaticExtensionsToGzip []string StaticExtensionsToGzip []string
StaticCacheFileSize int
StaticCacheFileNum int
TemplateLeft string TemplateLeft string
TemplateRight string TemplateRight string
ViewsPath string ViewsPath string
@ -236,6 +238,8 @@ func newBConfig() *Config {
DirectoryIndex: false, DirectoryIndex: false,
StaticDir: map[string]string{"/static": "static"}, StaticDir: map[string]string{"/static": "static"},
StaticExtensionsToGzip: []string{".css", ".js"}, StaticExtensionsToGzip: []string{".css", ".js"},
StaticCacheFileSize: 1024 * 100,
StaticCacheFileNum: 1000,
TemplateLeft: "{{", TemplateLeft: "{{",
TemplateRight: "}}", TemplateRight: "}}",
ViewsPath: "views", ViewsPath: "views",
@ -317,6 +321,14 @@ func assignConfig(ac config.Configer) error {
} }
} }
if sfs, err := ac.Int("StaticCacheFileSize"); err == nil {
BConfig.WebConfig.StaticCacheFileSize = sfs
}
if sfn, err := ac.Int("StaticCacheFileNum"); err == nil {
BConfig.WebConfig.StaticCacheFileNum = sfn
}
if lo := ac.String("LogOutputs"); lo != "" { if lo := ac.String("LogOutputs"); lo != "" {
// if lo is not nil or empty // if lo is not nil or empty
// means user has set his own LogOutputs // means user has set his own LogOutputs

View File

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
"strconv"
"strings" "strings"
"sync" "sync"
) )
@ -91,13 +92,7 @@ func (c *JSONConfigContainer) DefaultBool(key string, defaultval bool) bool {
// Int returns the integer value for a given key. // Int returns the integer value for a given key.
func (c *JSONConfigContainer) Int(key string) (int, error) { func (c *JSONConfigContainer) Int(key string) (int, error) {
val := c.getData(key) val := c.getData(key)
if val != nil { return strconv.Atoi(val.(string))
if v, ok := val.(float64); ok {
return int(v), nil
}
return 0, errors.New("not int value")
}
return 0, errors.New("not exist key:" + key)
} }
// DefaultInt returns the integer value for a given key. // DefaultInt returns the integer value for a given key.

View File

@ -115,6 +115,8 @@ func TestAssignConfig_03(t *testing.T) {
ac.Set("RunMode", "online") ac.Set("RunMode", "online")
ac.Set("StaticDir", "download:down download2:down2") ac.Set("StaticDir", "download:down download2:down2")
ac.Set("StaticExtensionsToGzip", ".css,.js,.html,.jpg,.png") ac.Set("StaticExtensionsToGzip", ".css,.js,.html,.jpg,.png")
ac.Set("StaticCacheFileSize", "87456")
ac.Set("StaticCacheFileNum", "1254")
assignConfig(ac) assignConfig(ac)
t.Logf("%#v", BConfig) t.Logf("%#v", BConfig)
@ -132,6 +134,12 @@ func TestAssignConfig_03(t *testing.T) {
if BConfig.WebConfig.StaticDir["/download2"] != "down2" { if BConfig.WebConfig.StaticDir["/download2"] != "down2" {
t.FailNow() t.FailNow()
} }
if BConfig.WebConfig.StaticCacheFileSize != 87456 {
t.FailNow()
}
if BConfig.WebConfig.StaticCacheFileNum != 1254 {
t.FailNow()
}
if len(BConfig.WebConfig.StaticExtensionsToGzip) != 5 { if len(BConfig.WebConfig.StaticExtensionsToGzip) != 5 {
t.FailNow() t.FailNow()
} }

View File

@ -28,6 +28,7 @@ import (
"github.com/astaxie/beego/context" "github.com/astaxie/beego/context"
"github.com/astaxie/beego/logs" "github.com/astaxie/beego/logs"
"github.com/hashicorp/golang-lru"
) )
var errNotStaticRequest = errors.New("request not a static file request") var errNotStaticRequest = errors.New("request not a static file request")
@ -93,10 +94,11 @@ func serverStaticRouter(ctx *context.Context) {
} }
type serveContentHolder struct { type serveContentHolder struct {
data []byte data []byte
modTime time.Time modTime time.Time
size int64 size int64
encoding string originSize int64 //original file size:to judge file changed
encoding string
} }
type serveContentReader struct { type serveContentReader struct {
@ -104,22 +106,36 @@ type serveContentReader struct {
} }
var ( var (
staticFileMap = make(map[string]*serveContentHolder) staticFileLruCache *lru.Cache
mapLock sync.RWMutex lruLock sync.RWMutex
) )
func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, *serveContentReader, error) { func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, string, *serveContentHolder, *serveContentReader, error) {
if staticFileLruCache == nil {
//avoid lru cache error
if BConfig.WebConfig.StaticCacheFileNum >= 1 {
staticFileLruCache, _ = lru.New(BConfig.WebConfig.StaticCacheFileNum)
} else {
staticFileLruCache, _ = lru.New(1)
}
}
mapKey := acceptEncoding + ":" + filePath mapKey := acceptEncoding + ":" + filePath
mapLock.RLock() lruLock.RLock()
mapFile := staticFileMap[mapKey] var mapFile *serveContentHolder
mapLock.RUnlock() if cacheItem, ok := staticFileLruCache.Get(mapKey); ok {
mapFile = cacheItem.(*serveContentHolder)
}
lruLock.RUnlock()
if isOk(mapFile, fi) { if isOk(mapFile, fi) {
reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)} reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil return mapFile.encoding != "", mapFile.encoding, mapFile, reader, nil
} }
mapLock.Lock() lruLock.Lock()
defer mapLock.Unlock() defer lruLock.Unlock()
if mapFile = staticFileMap[mapKey]; !isOk(mapFile, fi) { if cacheItem, ok := staticFileLruCache.Get(mapKey); ok {
mapFile = cacheItem.(*serveContentHolder)
}
if !isOk(mapFile, fi) {
file, err := os.Open(filePath) file, err := os.Open(filePath)
if err != nil { if err != nil {
return false, "", nil, nil, err return false, "", nil, nil, err
@ -130,8 +146,10 @@ func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, str
if err != nil { if err != nil {
return false, "", nil, nil, err return false, "", nil, nil, err
} }
mapFile = &serveContentHolder{data: bufferWriter.Bytes(), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), encoding: n} mapFile = &serveContentHolder{data: bufferWriter.Bytes(), modTime: fi.ModTime(), size: int64(bufferWriter.Len()), originSize: fi.Size(), encoding: n}
staticFileMap[mapKey] = mapFile if isOk(mapFile, fi) {
staticFileLruCache.Add(mapKey, mapFile)
}
} }
reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)} reader := &serveContentReader{Reader: bytes.NewReader(mapFile.data)}
@ -141,8 +159,10 @@ func openFile(filePath string, fi os.FileInfo, acceptEncoding string) (bool, str
func isOk(s *serveContentHolder, fi os.FileInfo) bool { func isOk(s *serveContentHolder, fi os.FileInfo) bool {
if s == nil { if s == nil {
return false return false
} else if s.size > int64(BConfig.WebConfig.StaticCacheFileSize) {
return false
} }
return s.modTime == fi.ModTime() && s.size == fi.Size() return s.modTime == fi.ModTime() && s.originSize == fi.Size()
} }
// isStaticCompress detect static files // isStaticCompress detect static files

View File

@ -4,6 +4,7 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"compress/zlib" "compress/zlib"
"fmt"
"io" "io"
"io/ioutil" "io/ioutil"
"os" "os"
@ -53,6 +54,31 @@ func TestOpenStaticFileDeflate_1(t *testing.T) {
testOpenFile("deflate", content, t) testOpenFile("deflate", content, t)
} }
func TestStaticCacheWork(t *testing.T) {
encodings := []string{"", "gzip", "deflate"}
fi, _ := os.Stat(licenseFile)
for _, encoding := range encodings {
_, _, first, _, err := openFile(licenseFile, fi, encoding)
if err != nil {
t.Error(err)
continue
}
_, _, second, _, err := openFile(licenseFile, fi, encoding)
if err != nil {
t.Error(err)
continue
}
address1 := fmt.Sprintf("%p", first)
address2 := fmt.Sprintf("%p", second)
if address1 != address2 {
t.Errorf("encoding '%v' can not hit cache", encoding)
}
}
}
func assetOpenFileAndContent(sch *serveContentHolder, reader *serveContentReader, content []byte, t *testing.T) { func assetOpenFileAndContent(sch *serveContentHolder, reader *serveContentReader, content []byte, t *testing.T) {
t.Log(sch.size, len(content)) t.Log(sch.size, len(content))
if sch.size != int64(len(content)) { if sch.size != int64(len(content)) {
@ -66,7 +92,7 @@ func assetOpenFileAndContent(sch *serveContentHolder, reader *serveContentReader
t.Fail() t.Fail()
} }
} }
if len(staticFileMap) == 0 { if staticFileLruCache.Len() == 0 {
t.Log("men map is empty") t.Log("men map is empty")
t.Fail() t.Fail()
} }