diff --git a/blog/blog.go b/blog/blog.go index 7c47e38..8e2a71b 100644 --- a/blog/blog.go +++ b/blog/blog.go @@ -2,6 +2,7 @@ package blog import ( "fmt" + "github.com/Kugelschieber/marvinblum.de/tpl" emvi "github.com/emvi/api-go" "github.com/emvi/logbuch" "io/ioutil" @@ -9,6 +10,7 @@ import ( "os" "path/filepath" "regexp" + "sync" "time" ) @@ -29,11 +31,13 @@ type Blog struct { articles map[string]emvi.Article // id -> article articlesYear map[int][]emvi.Article // year -> articles nextUpdate time.Time + cache *tpl.Cache + m sync.Mutex } -func NewBlog() *Blog { +func NewBlog(cache *tpl.Cache) *Blog { logbuch.Info("Initializing blog") - b := new(Blog) + b := &Blog{cache: cache} b.client = emvi.NewClient(os.Getenv("MB_EMVI_CLIENT_ID"), os.Getenv("MB_EMVI_CLIENT_SECRET"), os.Getenv("MB_EMVI_ORGA"), @@ -76,6 +80,8 @@ func (blog *Blog) GetLatestArticles() []emvi.Article { } func (blog *Blog) loadArticles() { + blog.m.Lock() + defer blog.m.Unlock() logbuch.Info("Refreshing blog articles...") articles, offset, count := make(map[string]emvi.Article), 0, 1 var err error @@ -188,6 +194,7 @@ func (blog *Blog) setArticles(articles map[string]emvi.Article) { func (blog *Blog) refreshIfRequired() { if blog.nextUpdate.Before(time.Now()) { + blog.cache.Clear() blog.loadArticles() } } diff --git a/main.go b/main.go index 38c16d0..5fb6ba7 100644 --- a/main.go +++ b/main.go @@ -31,6 +31,7 @@ const ( var ( tracker *pirsch.Tracker + tplCache *tpl.Cache blogInstance *blog.Blog ) @@ -88,32 +89,29 @@ func setupTracker() { func serveAbout() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tracker.Hit(r) - data := struct { + tplCache.Render(w, "about.html", struct { Articles []emvi.Article }{ blogInstance.GetLatestArticles(), - } + }) + } +} - if err := tpl.Get().ExecuteTemplate(w, "about.html", data); err != nil { - logbuch.Error("Error executing blog template", logbuch.Fields{"err": err}) - w.WriteHeader(http.StatusInternalServerError) - } +func serveLegal() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + tracker.Hit(r) + tplCache.Render(w, "legal.html", nil) } } func serveBlogPage() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { tracker.Hit(r) - data := struct { + tplCache.Render(w, "blog.html", struct { Articles map[int][]emvi.Article }{ blogInstance.GetArticles(), - } - - if err := tpl.Get().ExecuteTemplate(w, "blog.html", data); err != nil { - logbuch.Error("Error executing blog template", logbuch.Fields{"err": err}) - w.WriteHeader(http.StatusInternalServerError) - } + }) } } @@ -135,7 +133,7 @@ func serveBlogArticle() http.HandlerFunc { return } - data := struct { + tplCache.Render(w, "article.html", struct { Title string Content template.HTML Published time.Time @@ -143,12 +141,13 @@ func serveBlogArticle() http.HandlerFunc { article.LatestArticleContent.Title, template.HTML(article.LatestArticleContent.Content), article.Published, - } + }) + } +} - if err := tpl.Get().ExecuteTemplate(w, "article.html", data); err != nil { - logbuch.Error("Error executing blog article template", logbuch.Fields{"err": err}) - w.WriteHeader(http.StatusInternalServerError) - } +func serveNotFound() http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + tplCache.Render(w, "notfound.html", nil) } } @@ -157,9 +156,9 @@ func setupRouter() *mux.Router { router.PathPrefix(staticDirPrefix).Handler(http.StripPrefix(staticDirPrefix, gziphandler.GzipHandler(http.FileServer(http.Dir(staticDir))))) router.Handle("/blog/{slug}", serveBlogArticle()) router.Handle("/blog", serveBlogPage()) - router.Handle("/legal", tpl.ServeTemplate("legal.html", tracker)) + router.Handle("/legal", serveLegal()) router.Handle("/", serveAbout()) - router.NotFoundHandler = tpl.ServeTemplate("notfound.html", tracker) + router.NotFoundHandler = serveNotFound() return router } @@ -199,9 +198,9 @@ func start(handler http.Handler) { func main() { configureLog() logEnvConfig() - tpl.LoadTemplate() setupTracker() - blogInstance = blog.NewBlog() + tplCache = tpl.NewCache() + blogInstance = blog.NewBlog(tplCache) router := setupRouter() corsConfig := configureCors(router) start(corsConfig) diff --git a/tpl/template.go b/tpl/template.go index 226bce8..c13e53a 100644 --- a/tpl/template.go +++ b/tpl/template.go @@ -3,11 +3,11 @@ package tpl import ( "bytes" "github.com/emvi/logbuch" - "github.com/emvi/pirsch" "github.com/gosimple/slug" "html/template" "net/http" "os" + "sync" "time" ) @@ -15,61 +15,68 @@ const ( templateDir = "template/*" ) -var ( +type Cache struct { tpl *template.Template - tplCache = make(map[string][]byte) + cache map[string][]byte hotReload bool -) - -var funcMap = template.FuncMap{ - "slug": slug.Make, - "format": func(t time.Time, layout string) string { - return t.Format(layout) - }, + m sync.RWMutex } -func LoadTemplate() { +func NewCache() *Cache { + cache := &Cache{ + cache: make(map[string][]byte), + hotReload: os.Getenv("MB_HOT_RELOAD") == "true", + } + cache.load() + return cache +} + +func (cache *Cache) load() { logbuch.Debug("Loading templates") + funcMap := template.FuncMap{ + "slug": slug.Make, + "format": func(t time.Time, layout string) string { + return t.Format(layout) + }, + } var err error - tpl, err = template.New("").Funcs(funcMap).ParseGlob(templateDir) + cache.tpl, err = template.New("").Funcs(funcMap).ParseGlob(templateDir) if err != nil { logbuch.Fatal("Error loading template", logbuch.Fields{"err": err}) } - hotReload = os.Getenv("MB_HOT_RELOAD") == "true" - logbuch.Debug("Templates loaded", logbuch.Fields{"hot_reload": hotReload}) + logbuch.Debug("Templates loaded", logbuch.Fields{"hot_reload": cache.hotReload}) } -func renderTemplate(name string) { - logbuch.Debug("Rendering template", logbuch.Fields{"name": name}) - var buffer bytes.Buffer +func (cache *Cache) Render(w http.ResponseWriter, name string, data interface{}) { + cache.m.RLock() - if err := tpl.ExecuteTemplate(&buffer, name, nil); err != nil { - logbuch.Fatal("Error executing template", logbuch.Fields{"err": err, "name": name}) - } + if cache.cache[name] == nil || cache.hotReload { + cache.m.RUnlock() + cache.m.Lock() + defer cache.m.Unlock() + logbuch.Debug("Rendering template", logbuch.Fields{"name": name}) + var buffer bytes.Buffer - tplCache[name] = buffer.Bytes() -} - -func Get() *template.Template { - return tpl -} - -func ServeTemplate(name string, tracker *pirsch.Tracker) http.HandlerFunc { - // render once so we have it in cache - renderTemplate(name) - - return func(w http.ResponseWriter, r *http.Request) { - tracker.Hit(r) - - if hotReload { - LoadTemplate() - renderTemplate(name) + if err := cache.tpl.ExecuteTemplate(&buffer, name, data); err != nil { + logbuch.Error("Error executing template", logbuch.Fields{"err": err, "name": name}) + w.WriteHeader(http.StatusInternalServerError) } - if _, err := w.Write(tplCache[name]); err != nil { - logbuch.Error("Error returning page to client", logbuch.Fields{"err": err, "name": name}) - } + cache.cache[name] = buffer.Bytes() + } else { + cache.m.RUnlock() + } + + if _, err := w.Write(cache.cache[name]); err != nil { + logbuch.Error("Error sending response to client", logbuch.Fields{"err": err, "template": name}) + w.WriteHeader(http.StatusInternalServerError) } } + +func (cache *Cache) Clear() { + cache.m.Lock() + defer cache.m.Unlock() + cache.cache = make(map[string][]byte) +}