From efe7a0f3f02eaf893791f03043a5dc32d4f38ad4 Mon Sep 17 00:00:00 2001 From: Azareal Date: Sun, 28 Apr 2019 20:08:05 +1000 Subject: [PATCH] ETags are now set by /api/phrases and Hyperdrive on the topic list for better caching. Initialised the fields in hyperspace properly. Eliminate an unnecessary line in client templates. Cleaned up some thaw code in the forum store. Fixed a potential premature thaw in Forums.BypassGet --- common/files.go | 6 +-- common/forum_store.go | 5 +- common/template_init.go | 2 +- common/templates/templates.go | 6 ++- experimental/plugin_hyperdrive.go | 30 ++++++++++-- public/init.js | 2 +- routes.go | 80 ++++++++++++++++--------------- 7 files changed, 77 insertions(+), 54 deletions(-) diff --git a/common/files.go b/common/files.go index 75b13e8b..57372d8b 100644 --- a/common/files.go +++ b/common/files.go @@ -55,7 +55,7 @@ func (list SFileList) JSTmplInit() error { } path = strings.TrimPrefix(path, "tmpl_client/") - tmplName := strings.TrimSuffix(path, ".go") + tmplName := strings.TrimSuffix(path, ".jgo") shortName := strings.TrimPrefix(tmplName, "template_") var replace = func(data []byte, replaceThis string, withThis string) []byte { @@ -104,10 +104,6 @@ func (list SFileList) JSTmplInit() error { } return out + "]" }*/ - data = replace(data, `) - if !ok { - return errors.New("invalid page struct value") - }`, "*/tmpl_"+shortName+"_vars = tmpl_"+shortName+"_i") // ? Can we just use a regex? I'm thinking of going more efficient, or just outright rolling wasm, this is a temp hack in a place where performance doesn't particularly matter var each = func(phrase string, handle func(index int)) { diff --git a/common/forum_store.go b/common/forum_store.go index e14780a6..476f0110 100644 --- a/common/forum_store.go +++ b/common/forum_store.go @@ -143,6 +143,7 @@ func (mfs *MemoryForumStore) rebuildView() { }) sort.Sort(SortForum(forumView)) mfs.forumView.Store(forumView) + TopicListThaw.Thaw() } func (mfs *MemoryForumStore) DirtyGet(id int) *Forum { @@ -190,7 +191,7 @@ func (mfs *MemoryForumStore) BypassGet(id int) (*Forum, error) { forum.Link = BuildForumURL(NameToSlug(forum.Name), forum.ID) forum.LastTopic = Topics.DirtyGet(forum.LastTopicID) forum.LastReplyer = Users.DirtyGet(forum.LastReplyerID) - TopicListThaw.Thaw() + //TopicListThaw.Thaw() return forum, err } @@ -219,7 +220,6 @@ func (mfs *MemoryForumStore) Reload(id int) error { forum.LastReplyer = Users.DirtyGet(forum.LastReplyerID) mfs.CacheSet(forum) - TopicListThaw.Thaw() return nil } @@ -290,7 +290,6 @@ func (mfs *MemoryForumStore) Delete(id int) error { } _, err := mfs.delete.Exec(id) mfs.CacheDelete(id) - TopicListThaw.Thaw() return err } diff --git a/common/template_init.go b/common/template_init.go index aa45d6a1..6698e9e5 100644 --- a/common/template_init.go +++ b/common/template_init.go @@ -495,7 +495,7 @@ func compileJSTemplates(wg *sync.WaitGroup, c *tmpl.CTemplateSet, themeName stri if tname != "" { tname = "_" + tname } - err := writeFile(dirPrefix+"template_"+name+tname+".go", content) + err := writeFile(dirPrefix+"template_"+name+tname+".jgo", content) if err != nil { log.Fatal(err) } diff --git a/common/templates/templates.go b/common/templates/templates.go index 69dd2b74..0602e650 100644 --- a/common/templates/templates.go +++ b/common/templates/templates.go @@ -442,20 +442,22 @@ func (c *CTemplateSet) compile(name string, content string, expects string, expe fout += "}\n\n" } + if c.lang == "normal" { fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_i interface{}, w io.Writer) error {\n" fout += `tmpl_` + fname + `_vars, ok := tmpl_` + fname + `_i.(` + expects + `) if !ok { return errors.New("invalid page struct value") } ` - if c.lang == "normal" { - fout += `var iw http.ResponseWriter + fout += `var iw http.ResponseWriter gzw, ok := w.(common.GzipResponseWriter) if ok { iw = gzw.ResponseWriter } _ = iw ` + } else { + fout += "// nolint\nfunc Template_" + fname + "(tmpl_" + fname + "_vars interface{}, w io.Writer) error {\n" } if len(c.langIndexToName) > 0 { diff --git a/experimental/plugin_hyperdrive.go b/experimental/plugin_hyperdrive.go index d3168887..2643ebd9 100644 --- a/experimental/plugin_hyperdrive.go +++ b/experimental/plugin_hyperdrive.go @@ -5,6 +5,9 @@ import ( //"log" "bytes" "errors" + "strings" + "strconv" + "time" "sync/atomic" "net/http" "net/http/httptest" @@ -37,11 +40,14 @@ func deactivateHdrive(plugin *c.Plugin) { type Hyperspace struct { topicList atomic.Value gzipTopicList atomic.Value + lastTopicListUpdate atomic.Value } func newHyperspace() *Hyperspace { pageCache := new(Hyperspace) pageCache.topicList.Store([]byte("")) + pageCache.gzipTopicList.Store([]byte("")) + pageCache.lastTopicListUpdate.Store(int64(0)) return pageCache } @@ -86,6 +92,7 @@ func tickHdrive(args ...interface{}) (skip bool, rerr c.RouteError) { return false, nil } hyperspace.gzipTopicList.Store(gbuf) + hyperspace.lastTopicListUpdate.Store(time.Now().Unix()) return false, nil } @@ -128,12 +135,29 @@ func jumpHdrive(args ...interface{}) (skip bool, rerr c.RouteError) { //c.DebugLog c.DebugLog("Successful jump") + var etag string + lastUpdate := hyperspace.lastTopicListUpdate.Load().(int64) + c.DebugLog("lastUpdate:",lastUpdate) + if ok { + iw.Header().Set("X-I","1") + etag = "\""+strconv.FormatInt(lastUpdate, 10)+"-g\"" + } else { + etag = "\""+strconv.FormatInt(lastUpdate, 10)+"\"" + } + + if lastUpdate != 0 { + iw.Header().Set("ETag", etag) + if match := r.Header.Get("If-None-Match"); match != "" { + if strings.Contains(match, etag) { + iw.WriteHeader(http.StatusNotModified) + return true, nil + } + } + } + header := args[3].(*c.Header) routes.FootHeaders(w, header) iw.Write(tList) - if ok { - w.Header().Set("X-I","1") - } return true, nil } \ No newline at end of file diff --git a/public/init.js b/public/init.js index 7d35cbfe..544b46f1 100644 --- a/public/init.js +++ b/public/init.js @@ -185,7 +185,7 @@ function initPhrases(loggedIn, panel = false) { } function fetchPhrases(plist) { - fetch("/api/phrases/?query="+plist) + fetch("/api/phrases/?query="+plist, {cache: "no-cache"}) .then((resp) => resp.json()) .then((data) => { console.log("loaded phrase endpoint data"); diff --git a/routes.go b/routes.go index dacdb059..56656891 100644 --- a/routes.go +++ b/routes.go @@ -191,32 +191,50 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user c.User) c.Rout return c.PreErrorJS("You haven't requested any phrases", w, r) } + var etag string + _, ok := w.(c.GzipResponseWriter) + if ok { + etag = "\""+strconv.FormatInt(c.StartTime.Unix(), 10)+"-g\"" + } else { + etag = "\""+strconv.FormatInt(c.StartTime.Unix(), 10)+"\"" + } + var plist map[string]string + var posLoop = func(positive string) c.RouteError { + // ! Constrain it to a subset of phrases for now + for _, item := range phraseWhitelist { + if strings.HasPrefix(positive, item) { + // TODO: Break this down into smaller security boundaries based on control panel sections? + if strings.HasPrefix(positive,"panel") { + w.Header().Set("Cache-Control", "private") + ok = user.IsSuperMod + } else { + ok = true + w.Header().Set("ETag", etag) + if match := r.Header.Get("If-None-Match"); match != "" { + if strings.Contains(match, etag) { + w.WriteHeader(http.StatusNotModified) + return nil + } + } + } + break + } + } + if !ok { + return c.PreErrorJS("Outside of phrase prefix whitelist", w, r) + } + return nil + } + // A little optimisation to avoid copying entries from one map to the other, if we don't have to mutate it - // TODO: Reduce the amount of duplication here if len(positives) > 1 { plist = make(map[string]string) for _, positive := range positives { - // ! Constrain it to a subset of phrases for now - var ok = false - for _, item := range phraseWhitelist { - if strings.HasPrefix(positive, item) { - // TODO: Break this down into smaller security boundaries based on control panel sections? - if strings.HasPrefix(positive,"panel") { - if user.IsSuperMod { - ok = true - w.Header().Set("Cache-Control", "private") - } - } else { - ok = true - } - break - } + rerr := posLoop(positive) + if rerr != nil { + return rerr } - if !ok { - return c.PreErrorJS("Outside of phrase prefix whitelist", w, r) - } - pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positive) if !ok { return c.PreErrorJS("No such prefix", w, r) @@ -226,26 +244,10 @@ func routeAPIPhrases(w http.ResponseWriter, r *http.Request, user c.User) c.Rout } } } else { - // ! Constrain it to a subset of phrases for now - var ok = false - for _, item := range phraseWhitelist { - if strings.HasPrefix(positives[0], item) { - // TODO: Break this down into smaller security boundaries based on control panel sections? - if strings.HasPrefix(positives[0],"panel") { - if user.IsSuperMod { - ok = true - w.Header().Set("Cache-Control", "private") - } - } else { - ok = true - } - break - } + rerr := posLoop(positives[0]) + if rerr != nil { + return rerr } - if !ok { - return c.PreErrorJS("Outside of phrase prefix whitelist", w, r) - } - pPhrases, ok := phrases.GetTmplPhrasesByPrefix(positives[0]) if !ok { return c.PreErrorJS("No such prefix", w, r)