Added page cache middleware.
This commit is contained in:
parent
855d67409f
commit
30dced6315
@ -13,6 +13,9 @@ import (
|
||||
"goweb/config"
|
||||
"goweb/container"
|
||||
"goweb/funcmap"
|
||||
"goweb/middleware"
|
||||
|
||||
"github.com/eko/gocache/v2/marshaler"
|
||||
|
||||
"github.com/eko/gocache/v2/store"
|
||||
|
||||
@ -48,7 +51,7 @@ func NewController(c *container.Container) Controller {
|
||||
|
||||
func (t *Controller) RenderPage(c echo.Context, p Page) error {
|
||||
if p.Name == "" {
|
||||
c.Logger().Error("Page render failed due to missing name")
|
||||
c.Logger().Error("page render failed due to missing name")
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "Internal server error")
|
||||
}
|
||||
|
||||
@ -65,12 +68,17 @@ func (t *Controller) RenderPage(c echo.Context, p Page) error {
|
||||
return err
|
||||
}
|
||||
|
||||
t.cachePage(c, p)
|
||||
t.cachePage(c, p, buf)
|
||||
|
||||
// Set any headers
|
||||
for k, v := range p.Headers {
|
||||
c.Response().Header().Set(k, v)
|
||||
}
|
||||
|
||||
return c.HTMLBlob(p.StatusCode, buf.Bytes())
|
||||
}
|
||||
|
||||
func (t *Controller) cachePage(c echo.Context, p Page) {
|
||||
func (t *Controller) cachePage(c echo.Context, p Page, html *bytes.Buffer) {
|
||||
if !p.Cache.Enabled {
|
||||
return
|
||||
}
|
||||
@ -84,12 +92,19 @@ func (t *Controller) cachePage(c echo.Context, p Page) {
|
||||
Expiration: p.Cache.MaxAge,
|
||||
Tags: p.Cache.Tags,
|
||||
}
|
||||
err := t.Container.Cache.Set(c.Request().Context(), key, "my-value", opts)
|
||||
cp := middleware.CachedPage{
|
||||
HTML: html.Bytes(),
|
||||
Headers: p.Headers,
|
||||
StatusCode: p.StatusCode,
|
||||
}
|
||||
err := marshaler.New(t.Container.Cache).Set(c.Request().Context(), key, cp, opts)
|
||||
if err != nil {
|
||||
c.Logger().Errorf("Failed to cache page: %s", key)
|
||||
c.Logger().Errorf("failed to cache page: %s", key)
|
||||
c.Logger().Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
c.Logger().Infof("cached page for: %s", key)
|
||||
}
|
||||
|
||||
func (t *Controller) parsePageTemplates(p Page) error {
|
||||
@ -124,7 +139,7 @@ func (t *Controller) parsePageTemplates(p Page) error {
|
||||
func (t *Controller) executeTemplates(c echo.Context, p Page) (*bytes.Buffer, error) {
|
||||
tmpl, ok := templates.Load(p.Name)
|
||||
if !ok {
|
||||
c.Logger().Error("Uncached page template requested")
|
||||
c.Logger().Error("uncached page template requested")
|
||||
return nil, echo.NewHTTPError(http.StatusInternalServerError, "Internal server error")
|
||||
}
|
||||
|
||||
|
@ -33,9 +33,10 @@ type Page struct {
|
||||
Description string
|
||||
Keywords []string
|
||||
}
|
||||
Pager pager.Pager
|
||||
CSRF string
|
||||
Cache struct {
|
||||
Pager pager.Pager
|
||||
CSRF string
|
||||
Headers map[string]string
|
||||
Cache struct {
|
||||
Enabled bool
|
||||
MaxAge time.Duration
|
||||
Tags []string
|
||||
@ -49,6 +50,7 @@ func NewPage(c echo.Context) Page {
|
||||
Path: c.Request().URL.Path,
|
||||
StatusCode: http.StatusOK,
|
||||
Pager: pager.NewPager(c, DefaultItemsPerPage),
|
||||
Headers: make(map[string]string),
|
||||
}
|
||||
|
||||
p.IsHome = p.Path == "/"
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/labstack/echo-contrib/session"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
echomw "github.com/labstack/echo/v4/middleware"
|
||||
|
||||
@ -16,6 +17,12 @@ import (
|
||||
const StaticDir = "static"
|
||||
|
||||
func BuildRouter(c *container.Container) {
|
||||
// Static files with proper cache control
|
||||
// funcmap.File() should be used in templates to append a cache key to the URL in order to break cache
|
||||
// after each server restart
|
||||
c.Web.Group("", middleware.CacheControl(c.Config.Cache.MaxAge.StaticFile)).
|
||||
Static("/", StaticDir)
|
||||
|
||||
// Middleware
|
||||
c.Web.Use(echomw.RemoveTrailingSlashWithConfig(echomw.TrailingSlashConfig{
|
||||
RedirectCode: http.StatusMovedPermanently,
|
||||
@ -24,18 +31,14 @@ func BuildRouter(c *container.Container) {
|
||||
c.Web.Use(echomw.Recover())
|
||||
c.Web.Use(echomw.Gzip())
|
||||
c.Web.Use(echomw.Logger())
|
||||
c.Web.Use(echomw.TimeoutWithConfig(echomw.TimeoutConfig{
|
||||
Timeout: c.Config.App.Timeout,
|
||||
}))
|
||||
c.Web.Use(middleware.PageCache(c.Cache))
|
||||
c.Web.Use(session.Middleware(sessions.NewCookieStore([]byte(c.Config.App.EncryptionKey))))
|
||||
c.Web.Use(echomw.CSRFWithConfig(echomw.CSRFConfig{
|
||||
TokenLookup: "form:csrf",
|
||||
}))
|
||||
c.Web.Use(echomw.TimeoutWithConfig(echomw.TimeoutConfig{
|
||||
Timeout: c.Config.App.Timeout,
|
||||
}))
|
||||
// Static files with proper cache control
|
||||
// funcmap.File() should be used in templates to append a cache key to the URL in order to break cache
|
||||
// after each server restart
|
||||
c.Web.Group("", middleware.CacheControl(c.Config.Cache.MaxAge.StaticFile)).
|
||||
Static("/", StaticDir)
|
||||
|
||||
// Base controller
|
||||
ctr := NewController(c)
|
||||
|
2
go.mod
2
go.mod
@ -49,11 +49,13 @@ require (
|
||||
github.com/spf13/cast v1.3.1 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect
|
||||
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect
|
||||
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 // indirect
|
||||
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 // indirect
|
||||
google.golang.org/appengine v1.4.0 // indirect
|
||||
google.golang.org/protobuf v1.26.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect
|
||||
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
|
||||
|
2
go.sum
2
go.sum
@ -434,6 +434,7 @@ github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyC
|
||||
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
|
||||
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
|
||||
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
@ -613,6 +614,7 @@ gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZ
|
||||
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
|
||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
|
@ -4,9 +4,48 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/eko/gocache/v2/cache"
|
||||
"github.com/eko/gocache/v2/marshaler"
|
||||
"github.com/go-redis/redis/v8"
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type CachedPage struct {
|
||||
URL string
|
||||
HTML []byte
|
||||
StatusCode int
|
||||
Headers map[string]string
|
||||
}
|
||||
|
||||
func PageCache(ch *cache.Cache) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
key := c.Request().URL.String()
|
||||
res, err := marshaler.New(ch).Get(c.Request().Context(), key, new(CachedPage))
|
||||
if err != nil {
|
||||
if err == redis.Nil {
|
||||
c.Logger().Infof("no cached page for: %s", key)
|
||||
} else {
|
||||
c.Logger().Errorf("failed getting cached page: %s", key)
|
||||
c.Logger().Error(err)
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
|
||||
page := res.(*CachedPage)
|
||||
|
||||
if page.Headers != nil {
|
||||
for k, v := range page.Headers {
|
||||
c.Response().Header().Set(k, v)
|
||||
}
|
||||
}
|
||||
c.Logger().Infof("serving cached page for: %s", key)
|
||||
|
||||
return c.HTMLBlob(page.StatusCode, page.HTML)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func CacheControl(maxAge time.Duration) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
|
Loading…
Reference in New Issue
Block a user