diff --git a/service/middleware.go b/service/middleware.go index 2da2593f..9a6e45c1 100644 --- a/service/middleware.go +++ b/service/middleware.go @@ -55,11 +55,11 @@ func withStaticFileServer(c *gin.Context) { for _, rewrite := range rewriteRules { // log.Debug("Rewrite: %s => %s", c.Request.URL.Path, rewrite.Replacement) if matches := rewrite.Pattern.FindStringSubmatch(c.Request.URL.Path); matches != nil { - rewriteOriginalPath := c.Request.URL.Path c.Set("rewrite", true) c.Set("matches", matches) c.Request.URL.Path = rewrite.Pattern.ReplaceAllString(c.Request.URL.Path, rewrite.Replacement) - log.Trace("Rewrite FindStringSubmatch Matched: %s => %s", rewriteOriginalPath, rewrite.Replacement) + // rewriteOriginalPath := c.Request.URL.Path + // log.Trace("Rewrite FindStringSubmatch Matched: %s => %s", rewriteOriginalPath, rewrite.Replacement) break } } diff --git a/sui/api/request.go b/sui/api/request.go index d10147c5..93550fb9 100644 --- a/sui/api/request.go +++ b/sui/api/request.go @@ -7,6 +7,7 @@ import ( "path/filepath" "regexp" "strings" + "time" "github.com/fatih/color" "github.com/gin-gonic/gin" @@ -87,8 +88,8 @@ func (r *Request) Render() (string, int, error) { // The page is not cached message := fmt.Sprintf("[SUI] The page %s is not cached. file=%s DisableCache=%v", r.Request.URL.Path, r.File, r.Request.DisableCache()) - fmt.Println(color.YellowString(message)) - log.Warn(message) + go fmt.Println(color.YellowString(message)) + go log.Warn(message) // Read the file content, err := application.App.Read(r.File) @@ -104,6 +105,9 @@ func (r *Request) Render() (string, int, error) { guard := "" guardRedirect := "" configText := "" + cacheStore := "" + cacheTime := 0 + configSel := doc.Find("script[name=config]") if configSel != nil && configSel.Length() > 0 { configText = configSel.Text() @@ -124,6 +128,10 @@ func (r *Request) Render() (string, int, error) { guard = parts[0] guardRedirect = parts[1] } + + // Cache store + cacheStore = conf.CacheStore + cacheTime = conf.Cache } dataText := "" @@ -153,9 +161,11 @@ func (r *Request) Render() (string, int, error) { Guard: guard, GuardRedirect: guardRedirect, Config: configText, + CacheStore: cacheStore, + CacheTime: time.Duration(cacheTime) * time.Second, } - core.SetCache(r.File, c) - log.Trace("[SUI] The page %s is cached file=%s", r.Request.URL.Path, r.File) + go core.SetCache(r.File, c) + go log.Trace("[SUI] The page %s is cached file=%s", r.Request.URL.Path, r.File) } // Guard the page @@ -180,6 +190,16 @@ func (r *Request) Render() (string, int, error) { data["$global"] = global } + // Read from cache directly + key := fmt.Sprintf("page:%s:%s", r.Hash(), data.Hash()) + if !r.Request.DisableCache() && c.CacheTime > 0 && c.CacheStore != "" { + html, exists := c.GetHTML(key) + if exists { + log.Trace("[SUI] The page %s is cached %v file=%s", r.Request.URL.Path, c.CacheTime, r.File) + return html, 200, nil + } + } + // Set the page request data option := core.ParserOption{ Theme: r.Request.Theme, @@ -195,6 +215,11 @@ func (r *Request) Render() (string, int, error) { return "", 500, fmt.Errorf("render error, please re-complie the page %s", err.Error()) } + // Save to The Cache + if c.CacheTime > 0 && c.CacheStore != "" { + go c.SetHTML(key, html, c.CacheTime) + } + return html, 200, nil } diff --git a/sui/core/cache.go b/sui/core/cache.go new file mode 100644 index 00000000..f7f1cac0 --- /dev/null +++ b/sui/core/cache.go @@ -0,0 +1,113 @@ +package core + +import ( + "time" + + "github.com/yaoapp/gou/store" + "github.com/yaoapp/kun/log" +) + +// Cache the cache +type Cache struct { + Data string + Global string + Config string + Guard string + GuardRedirect string + HTML string + CacheStore string + CacheTime time.Duration +} + +const ( + saveCache uint8 = iota + removeCache +) + +type cacheData struct { + file string + cache *Cache + cmd uint8 +} + +// Caches the caches +var Caches = map[string]*Cache{} +var ch = make(chan *cacheData, 1) + +func init() { + go cacheWriter() +} + +func cacheWriter() { + for { + select { + case data := <-ch: + switch data.cmd { + case saveCache: + Caches[data.file] = data.cache + case removeCache: + delete(Caches, data.file) + } + } + } +} + +// SetCache set the cache +func SetCache(file string, cache *Cache) { + ch <- &cacheData{file, cache, saveCache} +} + +// GetCache get the cache +func GetCache(file string) *Cache { + if cache, has := Caches[file]; has { + return cache + } + return nil +} + +// RemoveCache remove the cache +func RemoveCache(file string) { + ch <- &cacheData{file, nil, removeCache} +} + +// CleanCache clean the cache +func CleanCache() { + Caches = map[string]*Cache{} +} + +// GetHTML get the html +func (c *Cache) GetHTML(hash string) (string, bool) { + + store, has := store.Pools[c.CacheStore] + if !has { + log.Warn(`[SUI] The cache store "%s" is not found`, c.CacheStore) + return "", false + } + + v, has := store.Get(hash) + if !has { + return "", false + } + + return v.(string), true +} + +// SetHTML set the html +func (c *Cache) SetHTML(hash, html string, ttl time.Duration) { + store, has := store.Pools[c.CacheStore] + if !has { + log.Warn(`[SUI] The cache store "%s" is not found`, c.CacheStore) + return + } + store.Set(hash, html, ttl) +} + +// DelHTML del the html +func (c *Cache) DelHTML(hash string) { + store, has := store.Pools[c.CacheStore] + if !has { + log.Warn(`[SUI] The cache store "%s" is not found`, c.CacheStore) + return + } + store.Del(hash) +} diff --git a/sui/core/data.go b/sui/core/data.go index 22e6f900..4908a852 100644 --- a/sui/core/data.go +++ b/sui/core/data.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "hash/fnv" "regexp" "strings" @@ -23,6 +24,13 @@ var options = []expr.Option{ expr.AllowUndefinedVariables(), } +// Hash get the hash of the data +func (data Data) Hash() string { + h := fnv.New64a() + h.Write([]byte(fmt.Sprintf("%v", data))) + return fmt.Sprintf("%x", h.Sum64()) +} + // New create a new expression func (data Data) New(stmt string) (*vm.Program, error) { diff --git a/sui/core/page.go b/sui/core/page.go index 26c81149..41a64f47 100644 --- a/sui/core/page.go +++ b/sui/core/page.go @@ -60,12 +60,14 @@ func (page *Page) GetConfig() *PageConfig { // ExportConfig export the config func (page *Page) ExportConfig() string { if page.Config == nil { - return "" + return fmt.Sprintf(`{"cache_store": "%s"}`, page.CacheStore) } config, err := jsoniter.MarshalToString(map[string]interface{}{ - "title": page.Config.Title, - "guard": page.Config.Guard, + "title": page.Config.Title, + "guard": page.Config.Guard, + "cache_store": page.CacheStore, + "cache": page.Config.Cache, }) if err != nil { diff --git a/sui/core/request.go b/sui/core/request.go index 5e114217..b79cc7ad 100644 --- a/sui/core/request.go +++ b/sui/core/request.go @@ -2,6 +2,7 @@ package core import ( "fmt" + "hash/fnv" "strings" jsoniter "github.com/json-iterator/go" @@ -10,19 +11,6 @@ import ( "github.com/yaoapp/kun/log" ) -// Cache the cache -type Cache struct { - Data string - Global string - Config string - Guard string - GuardRedirect string - HTML string -} - -// Caches the caches -var Caches = map[string]*Cache{} - // NewRequestMock is the constructor for Request. func NewRequestMock(mock *PageMock) *Request { if mock == nil { @@ -107,6 +95,13 @@ func GetTheme(cookies map[string]string) interface{} { return nil } +// Hash get the hash +func (r *Request) Hash() string { + h := fnv.New64a() + h.Write([]byte(fmt.Sprintf("%v", r))) + return fmt.Sprintf("%x", h.Sum64()) +} + // ExecStringMerge exec the string and merge the data func (r *Request) ExecStringMerge(data Data, raw string) error { @@ -380,26 +375,3 @@ func (url ReqeustURL) Map() Data { "path": url.Path, } } - -// SetCache set the cache -func SetCache(file string, cache *Cache) { - Caches[file] = cache -} - -// GetCache get the cache -func GetCache(file string) *Cache { - if cache, has := Caches[file]; has { - return cache - } - return nil -} - -// RemoveCache remove the cache -func RemoveCache(file string) { - delete(Caches, file) -} - -// CleanCache clean the cache -func CleanCache() { - Caches = map[string]*Cache{} -} diff --git a/sui/core/types.go b/sui/core/types.go index 1a43a3d8..e4ce05cb 100644 --- a/sui/core/types.go +++ b/sui/core/types.go @@ -12,6 +12,7 @@ type DSL struct { Guard string `json:"guard,omitempty"` Storage *Storage `json:"storage,omitempty"` Public *Public `json:"public,omitempty"` + CacheStore string `json:"cache_store,omitempty"` // The cache store Sid string `json:"-"` publicRoot string `json:"-"` } @@ -27,6 +28,7 @@ type Setting struct { type Page struct { Route string `json:"route"` Name string `json:"name,omitempty"` + CacheStore string `json:"-"` TemplateID string `json:"-"` SuiID string `json:"-"` Config *PageConfig `json:"-"` @@ -294,6 +296,8 @@ type PageConfig struct { type PageSetting struct { Title string `json:"title,omitempty"` Guard string `json:"guard,omitempty"` + CacheStore string `json:"cache_store,omitempty"` + Cache int `json:"cache,omitempty"` Description string `json:"description,omitempty"` SEO *PageSEO `json:"seo,omitempty"` } diff --git a/sui/storages/local/page.go b/sui/storages/local/page.go index 4d557bfb..f94babbb 100644 --- a/sui/storages/local/page.go +++ b/sui/storages/local/page.go @@ -447,6 +447,9 @@ func (page *Page) Load() error { page.Codes.CONF.Code = string(confCode) } + // Set the page CacheStore + page.CacheStore = page.tmpl.local.DSL.CacheStore + // Set the page document page.Document = page.tmpl.Document