aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMitchell Riedstra <mitch@riedstra.dev>2023-01-07 13:31:23 -0500
committerMitchell Riedstra <mitch@riedstra.dev>2023-01-07 13:31:23 -0500
commitca33a035c779ae14fb6330c8801c75f49dd1bb79 (patch)
treedeaabaf15d6d91079a68f247e46070399e4343ee
parent97dd660925434be537cd9a49a1d0c893b223e357 (diff)
downloadgo-website-ca33a035c779ae14fb6330c8801c75f49dd1bb79.tar.gz
go-website-ca33a035c779ae14fb6330c8801c75f49dd1bb79.tar.xz
Add an internal caching option. It performs quite well.v0.0.22
Also refactor and clean up most linter warnings.
-rw-r--r--.golangci.yml17
-rw-r--r--cmd/server/app.go13
-rw-r--r--cmd/server/auth.go7
-rw-r--r--cmd/server/conditionalMiddleware.go18
-rw-r--r--cmd/server/edit.go11
-rw-r--r--cmd/server/feed.go84
-rw-r--r--cmd/server/handlers.go59
-rw-r--r--cmd/server/main.go9
-rw-r--r--cmd/server/middleware.go22
-rw-r--r--example-site/index.md7
-rw-r--r--example-site/reIndex.md2
-rw-r--r--example-site/tpl/dashboard.md9
-rw-r--r--mapcache/main.go119
-rw-r--r--page/checkup.go2
-rw-r--r--page/misc.go48
-rw-r--r--page/page.go24
-rw-r--r--page/render.go61
-rw-r--r--page/renderJson.go77
-rw-r--r--page/renderMarkdown.go32
-rw-r--r--rediscache/main.go2
20 files changed, 395 insertions, 228 deletions
diff --git a/.golangci.yml b/.golangci.yml
index ad60981..1d2d29f 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,27 +1,24 @@
linters:
enable:
- - deadcode
+ - govet
+ - unused
- dupl
- errcheck
- funlen
- # - gochecknoglobals
- ineffassign
- - structcheck
- typecheck
- - varcheck
- asciicheck
- bodyclose
- depguard
- dogsled
- errorlint
- exhaustive
- # - exhaustivestruct
- exportloopref
- gci
- gochecknoinits
- gocognit
- # - goconst
+ - goconst
- gocritic
- gocyclo
- godot
@@ -29,14 +26,12 @@ linters:
- gofmt
# - gofumpt
- goheader
- # - goimports
- - golint
+ - goimports
+ - revive
- gomnd
- gomodguard
- goprintffuncname
- - interfacer
- lll
- - maligned
- misspell
- nakedret
- nestif
@@ -46,7 +41,7 @@ linters:
- paralleltest
- prealloc
- rowserrcheck
- - scopelint
+ - exportloopref
- sqlclosecheck
- stylecheck
- testpackage
diff --git a/cmd/server/app.go b/cmd/server/app.go
index 9a00628..c694753 100644
--- a/cmd/server/app.go
+++ b/cmd/server/app.go
@@ -7,14 +7,17 @@ import (
"github.com/gomodule/redigo/redis"
"gopkg.in/yaml.v3"
+ "riedstra.dev/mitch/go-website/mapcache"
"riedstra.dev/mitch/go-website/page"
)
var FeedPrefixDefault = ".feeds"
type App struct {
+ mapCache bool
redisPool *redis.Pool
RedisKey string
+ cache *mapcache.Cache
ReIndexPath string
StaticDirectory string
@@ -86,6 +89,16 @@ func loadConf(fn string) (*App, error) {
return app, nil
}
+func (a *App) ClearCache() error {
+ if a.redisPool != nil {
+ return a.ClearRedis()
+ }
+
+ a.cache.Clear()
+
+ return nil
+}
+
// ClearRedis is a little helper function that allows us to easily clear
// the redis cache at runtime.
func (a *App) ClearRedis() error {
diff --git a/cmd/server/auth.go b/cmd/server/auth.go
index 635b6e2..1adc366 100644
--- a/cmd/server/auth.go
+++ b/cmd/server/auth.go
@@ -13,6 +13,9 @@ import (
"riedstra.dev/mitch/go-website/users"
)
+const tokenKeyBytes = 128 / 8
+const authConfFileMode = 0600
+
type Auth struct {
Users []*users.SiteUser `json:"Users"`
// How long are JWTs valid?
@@ -29,7 +32,7 @@ type Auth struct {
}
func GenTokenKey() string {
- r := make([]byte, 16) // 128 bits
+ r := make([]byte, tokenKeyBytes)
_, err := rand.Read(r)
if err != nil {
@@ -89,7 +92,7 @@ func (a *App) ReadAuth(fn string) error { //nolint
}
write:
- fh, err = os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0600)
+ fh, err = os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, authConfFileMode)
if err != nil {
return fmt.Errorf("opening file %s: %w", fn, err)
diff --git a/cmd/server/conditionalMiddleware.go b/cmd/server/conditionalMiddleware.go
new file mode 100644
index 0000000..60b1398
--- /dev/null
+++ b/cmd/server/conditionalMiddleware.go
@@ -0,0 +1,18 @@
+package main
+
+import "net/http"
+
+type conditionalMiddlewareFunc func(r *http.Request) bool
+
+// conditionalMiddleware simply uses handler a if condition is true and handler
+// b if condition is false.
+func conditionalMiddleware(condition conditionalMiddlewareFunc,
+ a, b http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ if condition(r) {
+ a.ServeHTTP(w, r)
+ } else {
+ b.ServeHTTP(w, r)
+ }
+ })
+}
diff --git a/cmd/server/edit.go b/cmd/server/edit.go
index 10d109a..5b40f51 100644
--- a/cmd/server/edit.go
+++ b/cmd/server/edit.go
@@ -14,17 +14,19 @@ import (
"riedstra.dev/mitch/go-website/page"
)
+const editFileMode = 0644
+
func (a *App) EditPage() http.Handler {
getFunc := a.GetEditPage().ServeHTTP
postFunc := a.SaveEditPage().ServeHTTP
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.Method {
- case "GET":
+ case "GET": //nolint:goconst
getFunc(w, r)
return
- case "POST":
+ case "POST": //nolint:goconst
postFunc(w, r)
return
@@ -98,7 +100,8 @@ func (a *App) SaveEditPage() http.Handler {
fn := "./" + p + page.Suffix
- fh, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
+ fh, err := os.OpenFile(fn, os.O_RDWR|os.O_CREATE|os.O_TRUNC,
+ editFileMode)
if err != nil {
log.Printf("opening file %s for writing: %s", fn, err)
a.Err500Default(w, r)
@@ -119,7 +122,7 @@ func (a *App) SaveEditPage() http.Handler {
http.Redirect(w, r, "/"+r.URL.Path, http.StatusFound)
- err = a.ClearRedis() // Clear out the cache if any
+ err = a.ClearCache() // Clear out the cache if any
if err != nil {
log.Printf("after editing %s: %s", fn, err)
}
diff --git a/cmd/server/feed.go b/cmd/server/feed.go
index 7e36cb3..822d13a 100644
--- a/cmd/server/feed.go
+++ b/cmd/server/feed.go
@@ -16,7 +16,7 @@ import (
type Author struct {
Name string `xml:"name"` // Required
- Uri string `xml:"uri,omitempty"` //nolint:golint,stylecheck
+ Uri string `xml:"uri,omitempty"` //nolint:revive,stylecheck
Email string `xml:"email,omitempty"`
}
@@ -36,7 +36,7 @@ type Content struct {
type Entry struct {
// Spec requires this, autogenerated from Title and updated if otherwise
// left empty
- Id string `xml:"id"` //nolint:golint,stylecheck
+ Id string `xml:"id"` //nolint:revive,stylecheck
Title string `xml:"title"` // Required
Updated *time.Time `xml:"updated"` // Required
@@ -68,10 +68,10 @@ func (i Entry) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
i2 := (*Alias)(&i)
- return e.EncodeElement(i2, start)
+ return e.EncodeElement(i2, start) //nolint:wrapcheck
}
-//nolint:stylecheck,golint
+//nolint:stylecheck,revive
type Atom struct {
Ns string `xml:"xmlns,attr"`
Title string `xml:"title"` // Required
@@ -109,7 +109,7 @@ func (a Atom) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
a2 := (*Alias)(&a)
- return e.EncodeElement(a2, start)
+ return e.EncodeElement(a2, start) //nolint:wrapcheck
}
// FeedHandler takes care of pulling from the index all of the relevant posts
@@ -119,8 +119,8 @@ func (a Atom) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
//
// "content" if unset, or set to false content is omitted from the feed
// "limit=n" stop at "n" and return the feed.
-func (a *App) FeedHandler() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //nolint:funlen
+func (a *App) FeedHandler() http.Handler { //nolint:funlen,gocognit
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
var (
addContent bool
limit int
@@ -179,40 +179,10 @@ func (a *App) FeedHandler() http.Handler {
break
}
- if !p.Published {
- continue
- }
-
- content := &bytes.Buffer{}
-
- err := p.Render(content)
+ err = a.addPageToEntries(&entries, p, addContent)
if err != nil {
- log.Println(err)
http.Error(w, "Internal server error", http.StatusInternalServerError)
-
- return
- }
-
- entry := Entry{
- Title: p.Title(),
- Updated: &p.Date.Time,
- Links: []Link{{Href: strings.Join([]string{a.SiteURL, p.Path()}, "/")}},
- }
-
- if p.AuthorName != "" {
- entry.Author = &Author{
- Name: p.AuthorName,
- }
- if p.AuthorEmail != "" {
- entry.Author.Email = p.AuthorEmail
- }
- }
-
- if addContent {
- entry.Content = &Content{Type: "html", Data: content.String()}
}
-
- entries = append(entries, entry)
}
feed.Entries = entries
@@ -237,3 +207,41 @@ func (a *App) FeedHandler() http.Handler {
}
})
}
+
+func (a *App) addPageToEntries(target *[]Entry, p *page.Page, addContent bool) error {
+ if !p.Published {
+ return nil
+ }
+
+ content := &bytes.Buffer{}
+
+ err := p.Render(content)
+ if err != nil {
+ log.Println(err)
+
+ return err //nolint:wrapcheck
+ }
+
+ entry := Entry{
+ Title: p.Title(),
+ Updated: &p.Date.Time,
+ Links: []Link{{Href: strings.Join([]string{a.SiteURL, p.Path()}, "/")}},
+ }
+
+ if p.AuthorName != "" {
+ entry.Author = &Author{
+ Name: p.AuthorName,
+ }
+ if p.AuthorEmail != "" {
+ entry.Author.Email = p.AuthorEmail
+ }
+ }
+
+ if addContent {
+ entry.Content = &Content{Type: "html", Data: content.String()}
+ }
+
+ *target = append(*target, entry)
+
+ return nil
+}
diff --git a/cmd/server/handlers.go b/cmd/server/handlers.go
index 3c767a4..bb174d8 100644
--- a/cmd/server/handlers.go
+++ b/cmd/server/handlers.go
@@ -6,6 +6,7 @@ import (
"path/filepath"
"strings"
+ "riedstra.dev/mitch/go-website/mapcache"
"riedstra.dev/mitch/go-website/page"
"riedstra.dev/mitch/go-website/rediscache"
)
@@ -28,17 +29,29 @@ func (a *App) addCacheableRoutes(r *http.ServeMux) *http.ServeMux {
strings.TrimSuffix(strings.TrimPrefix(a.FeedPrefix, "/"), "/"))
routes := map[string]http.Handler{
- "/_json/": a.PageJsonHandler(),
+ "/_json/": a.PageJSONHandler(),
"/_md/": a.PageMarkdownHandler(),
a.FeedPrefix: http.StripPrefix(a.FeedPrefix, a.FeedHandler()),
"/": a.PageHandler(),
}
+ if a.cache == nil {
+ a.cache = mapcache.New()
+ }
+
for route, handler := range routes {
- if a.redisPool != nil {
- r.Handle(route, rediscache.Handle(a.redisPool, a.RedisKey,
- handler))
- } else {
+ switch {
+ case a.mapCache:
+ r.Handle(route, conditionalMiddleware(
+ a.IsLoggedIn,
+ handler,
+ a.cache.Handle(handler)))
+ case a.redisPool != nil:
+ r.Handle(route, conditionalMiddleware(
+ a.IsLoggedIn,
+ handler,
+ rediscache.Handle(a.redisPool, a.RedisKey, handler)))
+ default:
r.Handle(route, handler)
}
}
@@ -52,10 +65,7 @@ func (a *App) Handler() http.Handler {
func (a *App) PageHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- u := r.URL.Path
- if u == "/" {
- u = "/index"
- }
+ u := page.GetURLPath(r)
loggedIn := a.IsLoggedIn(r)
@@ -68,7 +78,7 @@ func (a *App) PageHandler() http.Handler {
!loggedIn {
page.Render4xx(w, r, map[string]interface{}{
"LoggedIn": loggedIn,
- }, 404)
+ }, http.StatusNotFound)
return
}
@@ -81,52 +91,43 @@ func (a *App) PageHandler() http.Handler {
})
}
-func (a *App) PageJsonHandler() http.Handler {
+func (a *App) PageJSONHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- u := r.URL.Path
- if u == "/" {
- u = "/index"
- }
+ u := page.GetURLPath(r)
// Skip template directory
if strings.HasPrefix(u[1:], filepath.Clean(page.TemplateDirectory)) {
- page.Render4xx(w, r, map[string]interface{}{}, 404)
+ page.Render4xx(w, r, map[string]interface{}{}, http.StatusNotFound)
+
return
}
u = filepath.Join(".", u)
- page.RenderJson(w, r, u, map[string]interface{}{}, 200)
+ page.RenderJSON(w, r, u, map[string]interface{}{}, http.StatusOK)
})
}
func (a *App) PageMarkdownHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- u := r.URL.Path
- if u == "/" {
- u = "/index"
- }
+ u := page.GetURLPath(r)
// Skip template directory
if strings.HasPrefix(u[1:], filepath.Clean(page.TemplateDirectory)) {
- page.Render4xx(w, r, map[string]interface{}{}, 404)
+ page.Render4xx(w, r, map[string]interface{}{}, http.StatusNotFound)
+
return
}
u = filepath.Join(".", u)
- page.RenderMarkdown(w, r, u, map[string]interface{}{}, 200)
+ page.RenderMarkdown(w, r, u, map[string]interface{}{}, http.StatusOK)
})
}
func (a *App) RebuildIndexHandler() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- u := r.URL.Path
- if u == "/" {
- u = "/index"
- }
-
- u = filepath.Join(".", u)
+ u := page.GetPagePath(r)
p := page.NewPage("index")
err := p.RebuildIndex()
diff --git a/cmd/server/main.go b/cmd/server/main.go
index d50c468..10bbbf0 100644
--- a/cmd/server/main.go
+++ b/cmd/server/main.go
@@ -44,6 +44,7 @@ func main() { //nolint:funlen
redisKey = "go-website"
pageTimeout = 15
genhash = false
+ mapcache = false
)
fl := flag.NewFlagSet("Website", flag.ExitOnError)
@@ -73,6 +74,8 @@ func main() { //nolint:funlen
"If set to false, do not cache the page index"))
logIfErr(envflag.Bool(fl, &genhash, "genhash", "INTERACTIVE_HASH_GEN",
"If set to true, interactively generate a password hash"))
+ logIfErr(envflag.Bool(fl, &mapcache, "mapcache", "USE_MAP_CACHE",
+ "Instead of utilizing redis, utilize a map internally for caching"))
_ = fl.Parse(os.Args[1:])
@@ -96,6 +99,10 @@ func main() { //nolint:funlen
app = &App{}
}
+ if mapcache {
+ app.mapCache = true
+ }
+
err = app.ReadAuth(authConfFn)
if err != nil {
logger.Println(err)
@@ -134,7 +141,7 @@ func main() { //nolint:funlen
os.Stderr.Write(b)
}
- page.Funcs["ClearRedis"] = app.ClearRedis
+ page.Funcs["ClearCache"] = app.ClearCache
srv := &http.Server{
Handler: app.Handler(),
diff --git a/cmd/server/middleware.go b/cmd/server/middleware.go
index d0957fd..17717c2 100644
--- a/cmd/server/middleware.go
+++ b/cmd/server/middleware.go
@@ -2,6 +2,7 @@ package main
import (
"errors"
+ "fmt"
"log"
"net/http"
"net/url"
@@ -12,6 +13,8 @@ import (
"riedstra.dev/mitch/go-website/users"
)
+var ErrInvalidJWTToken = errors.New("invalid JWT token")
+
func (a *App) Err5xx(w http.ResponseWriter, r *http.Request,
statusCode int, title, desc string) {
page.Render5xx(w, r, map[string]interface{}{
@@ -33,15 +36,15 @@ func (a *App) LogoutHandler() http.Handler {
SameSite: a.auth.SameSiteStrict,
Secure: a.auth.Secure,
Value: "logout",
- Expires: time.Now().Add(time.Second), //nolint
+ Expires: time.Now().Add(time.Second),
})
http.Redirect(w, r, "/", http.StatusFound)
})
}
-func (a *App) LoginHandler() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { //nolint
+func (a *App) LoginHandler() http.Handler { //nolint
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
loggedIn := a.IsLoggedIn(r)
next, _ := url.Parse(r.URL.Query().Get("next"))
@@ -74,7 +77,7 @@ func (a *App) LoginHandler() http.Handler {
password := r.FormValue("password")
var (
- err error = nil
+ err error
u *users.SiteUser
found = false
)
@@ -125,16 +128,19 @@ func (a *App) IsLoggedIn(r *http.Request) bool {
_, err := a.GetAuthToken(r)
if err != nil {
log.Printf("%s IsLoggedIn: false", r.URL.Path)
+
return false
}
+
log.Printf("%s IsLoggedIn: true", r.URL.Path)
+
return true
}
func (a *App) GetAuthToken(r *http.Request) (*jwt.Token, error) {
c, err := r.Cookie("Auth")
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("getting auth token: %w", err)
}
token, err := jwt.Parse(c.Value,
@@ -144,11 +150,11 @@ func (a *App) GetAuthToken(r *http.Request) (*jwt.Token, error) {
)
if err != nil {
- return nil, err
+ return nil, fmt.Errorf("while parsing jwt %w", err)
}
if !token.Valid {
- return token, errors.New("IsLoggedIn: token not valid")
+ return token, ErrInvalidJWTToken
}
return token, nil
@@ -156,7 +162,6 @@ func (a *App) GetAuthToken(r *http.Request) (*jwt.Token, error) {
func (a *App) RequiresLogin(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-
if !a.IsLoggedIn(r) {
log.Printf("Unauthorized request %s %s", r.Method, r.URL.Path)
page.Render(w, r, "login", map[string]interface{}{
@@ -167,6 +172,5 @@ func (a *App) RequiresLogin(next http.Handler) http.Handler {
}
next.ServeHTTP(w, r)
-
})
}
diff --git a/example-site/index.md b/example-site/index.md
index f9f8fa8..7d51067 100644
--- a/example-site/index.md
+++ b/example-site/index.md
@@ -19,7 +19,7 @@ it.
## Recent Blog Entries:
-[Atom Feed](/{{.Global.App.FeedPrefix}}/Blog?limit=5&content)
+[Atom Feed]({{.Global.App.FeedPrefix}}/Blog?limit=5&content)
{{range $val := .Index.Blog.SortDate}}
* [{{$val.Date.Time.Format "2006-01-02"}} {{$val.Title}}]({{$val.Path}}){{end}}
@@ -27,7 +27,7 @@ it.
## Published Blog Entries:
{{range $val := .Index.Blog.SortDate}}{{if $val.Published}}
- * [{{$val.Date.Time.Format "2006-01-02"}} * {{$val.Title}}]({{$val.Path}}){{end}}{{end}}
+ * [{{$val.Date.Time.Format "2006-01-02"}} {{$val.Title}}]({{$val.Path}}){{end}}{{end}}
## Some internals
@@ -44,3 +44,6 @@ it.
{{.EncodeYaml .Index}}
```
+
+
+
diff --git a/example-site/reIndex.md b/example-site/reIndex.md
index 88cb278..d1938eb 100644
--- a/example-site/reIndex.md
+++ b/example-site/reIndex.md
@@ -6,7 +6,7 @@ description: >
# {{.Title}}
-{{.Global.App.ClearRedis}}
+{{.Global.App.ClearCache}}
this is the reindex page
diff --git a/example-site/tpl/dashboard.md b/example-site/tpl/dashboard.md
index 9b90445..4b18b98 100644
--- a/example-site/tpl/dashboard.md
+++ b/example-site/tpl/dashboard.md
@@ -44,6 +44,14 @@ Pages by tags:
{{end}}
+It can also be a good idea to clear the cache on the dashboard page:
+
+```
+{{.Global.App.ClearCache}}
+```
+
+The element can be hidden, if you'd like.
+
<script>
/*
window.addEventListener('load', (event) => {
@@ -51,3 +59,4 @@ window.addEventListener('load', (event) => {
});
*/
</script>
+
diff --git a/mapcache/main.go b/mapcache/main.go
new file mode 100644
index 0000000..0b6b697
--- /dev/null
+++ b/mapcache/main.go
@@ -0,0 +1,119 @@
+// Package mapcache is very similar to rediscache, except there are no
+// external dependencies and the page output is saved in a map
+package mapcache
+
+import (
+ "bytes"
+ "net/http"
+ "sync"
+)
+
+type responseWriter struct {
+ Headers http.Header
+ StatusCode int
+ Data []byte
+ buf *bytes.Buffer
+}
+
+func (rw *responseWriter) Header() http.Header {
+ if rw.Headers == nil {
+ rw.Headers = http.Header{}
+ }
+
+ return rw.Headers
+}
+
+func (rw *responseWriter) Write(msg []byte) (int, error) {
+ if rw.buf == nil {
+ rw.buf = &bytes.Buffer{}
+ }
+
+ return rw.buf.Write(msg) //nolint:wrapcheck
+}
+
+// Simply for satisfying the http.ResponseWriter interface.
+func (rw *responseWriter) WriteHeader(code int) {
+ rw.StatusCode = code
+}
+
+// WriteData takes the internal buffer and writes out the entire
+// contents to the 'Data' field for storage in Redis.
+func (rw *responseWriter) WriteData() {
+ rw.Data = rw.buf.Bytes()
+}
+
+func New() *Cache {
+ return &Cache{
+ m: &sync.RWMutex{},
+ cached: map[string]*responseWriter{},
+ }
+}
+
+type Cache struct {
+ m *sync.RWMutex
+ cached map[string]*responseWriter
+}
+
+func (c *Cache) Remove(path string) {
+ c.m.Lock()
+ delete(c.cached, path)
+ c.m.Unlock()
+}
+
+func (c *Cache) Clear() {
+ c.m.Lock()
+ c.cached = map[string]*responseWriter{}
+ c.m.Unlock()
+}
+
+// HandleWithParams is the same as Handle but caches for the GET params
+// rather than discarding them.
+func (c *Cache) HandleWithParams(next http.Handler) http.Handler {
+ return c.handle(true, next)
+}
+
+// Handle is a Simple function that will cache the response for given handler in
+// redis and instead of responding with the result from the handler it will
+// simply dump the contents of the redis key if it exists.
+func (c *Cache) Handle(next http.Handler) http.Handler {
+ return c.handle(false, next)
+}
+
+func (c *Cache) handle(params bool, next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ subkey := r.URL.Path
+ if params {
+ subkey = r.URL.Path + "?" + r.URL.RawQuery
+ }
+
+ content:
+ c.m.RLock()
+ rw, ok := c.cached[subkey]
+ c.m.RUnlock()
+ if !ok {
+ rw := &responseWriter{}
+ next.ServeHTTP(rw, r)
+
+ rw.WriteData()
+
+ c.m.Lock()
+ c.cached[subkey] = rw
+ c.m.Unlock()
+
+ // We got the content, let's go back around again and dump
+ // it out from the cache
+ goto content
+ }
+
+ if rw.Headers != nil {
+ for k, v := range rw.Headers {
+ w.Header()[k] = v
+ }
+ }
+
+ if rw.StatusCode != 0 {
+ w.WriteHeader(rw.StatusCode)
+ }
+ _, _ = w.Write(rw.Data)
+ })
+}
diff --git a/page/checkup.go b/page/checkup.go
index b3b71fc..c6ca447 100644
--- a/page/checkup.go
+++ b/page/checkup.go
@@ -21,7 +21,7 @@ func (p *Page) Checkup() (map[string]PageList, error) {
return err
}
- if !info.IsDir() && strings.HasSuffix(info.Name(), Suffix) {
+ if !info.IsDir() && strings.HasSuffix(info.Name(), Suffix) { //nolint:nestif
p2 := NewPage(strings.ReplaceAll(path, Suffix, ""))
err = p2.Read()
diff --git a/page/misc.go b/page/misc.go
index e603458..4b67945 100644
--- a/page/misc.go
+++ b/page/misc.go
@@ -2,10 +2,58 @@ package page
import (
"encoding/json"
+ "net/http"
+ "path/filepath"
"gopkg.in/yaml.v3"
)
+// logReq just maks that we've done a request in the log, format is roughly:
+// remoteAddr, Method, statusCode, page path.
+func logReq(r *http.Request, statusCode int) {
+ Logger.Printf("%s %s %d %s",
+ r.RemoteAddr,
+ r.Method,
+ statusCode,
+ GetPagePath(r))
+}
+
+// logErr logs messages in the format of:
+// RemoteAddr, Method, path: pagePath, message, error.
+func logErr(r *http.Request, msg string, err error) {
+ Logger.Printf("%s %s path: %s %s: %s",
+ r.RemoteAddr,
+ r.Method,
+ GetPagePath(r),
+ msg,
+ err)
+}
+
+// plainResp sends a text/plain response down to the writer with the appropriate
+// status code and a body of msg.
+func plainResp(w http.ResponseWriter, statusCode int, msg string) {
+ w.Header().Set("Content-type", "text/plain")
+ w.WriteHeader(statusCode)
+ _, _ = w.Write([]byte(msg))
+}
+
+// GetURLPath returns r.URL.Path, unless it's merely a slash, then
+// it returns "index". Usefulf for defining your own handlers.
+func GetURLPath(r *http.Request) string {
+ u := r.URL.Path
+ if u == "/" {
+ u = "/index"
+ }
+
+ return u
+}
+
+// GetPagePath is the same as GetURLPath except that it joins it with
+// the current directory ".". Also useful in defining your own handlers.
+func GetPagePath(r *http.Request) string {
+ return filepath.Join(".", GetURLPath(r))
+}
+
// EncodeYaml is meant to be used in templating functions to encode
// arbitrary information as a yaml string.
func (p Page) EncodeYaml(data interface{}) string {
diff --git a/page/page.go b/page/page.go
index 214021e..03d1f1e 100644
--- a/page/page.go
+++ b/page/page.go
@@ -59,7 +59,7 @@ type Page struct {
Vars map[string]interface{}
// Keys of vars to be included when RenderJson is called, all vars are
// omitted if empty.
- JsonVars []string
+ JSONVars []string
markdown []byte
}
@@ -69,7 +69,7 @@ type Page struct {
var Global interface{}
// Funcs accessible to the templates.
-var Funcs template.FuncMap = template.FuncMap{
+var Funcs = template.FuncMap{
"join": strings.Join,
"split": strings.Split,
"toLower": strings.ToLower,
@@ -191,7 +191,12 @@ func (p *Page) Render(wr io.Writer) error {
t = t.Funcs(Funcs)
- return t.Execute(wr, p)
+ err = t.Execute(wr, p)
+ if err != nil {
+ return fmt.Errorf("while executing template: %w", err)
+ }
+
+ return nil
}
// Read in the special markdown file format for the website off of the disk.
@@ -253,6 +258,7 @@ func (p *Page) Read() error {
// this is called in the base template.
func (p *Page) RenderBody() (string, error) {
s, err := p.GetMarkdown()
+
return string(blackfriday.Run([]byte(s))), err
}
@@ -278,3 +284,15 @@ func (p *Page) GetMarkdown() (string, error) {
func (p Page) String() string {
return fmt.Sprintf("Page: %s", p.path)
}
+
+// setRenderingMarkdownOnly simply sets the special vars field,
+// "RenderingMarkdownOnly" to be true.
+func (p *Page) setRenderingMarkdownOnly() {
+ if p.Vars != nil {
+ p.Vars["RenderingMarkdownOnly"] = true
+ } else {
+ p.Vars = map[string]interface{}{
+ "RenderingMarkdownOnly": true,
+ }
+ }
+}
diff --git a/page/render.go b/page/render.go
index 691a258..0ae5334 100644
--- a/page/render.go
+++ b/page/render.go
@@ -5,43 +5,21 @@ import (
"errors"
"io/fs"
"net/http"
- "path/filepath"
)
-func getURLPath(r *http.Request) string {
- u := r.URL.Path
- if u == "/" {
- u = "/index"
- }
-
- return u
-}
-
// Render5xx is automatically called if any render fails,
// additionally you can call it for your own uses. It will
// try to use the 5xx template but will fall back to a plain
// page if that fails.
func Render5xx(w http.ResponseWriter, r *http.Request,
vars map[string]interface{}, statusCode int) {
- u := getURLPath(r)
-
- Logger.Printf("%s %s %d %s",
- r.RemoteAddr,
- r.Method,
- statusCode,
- u)
-
p := NewPage(TemplateDirectory + "/5xx")
buf := &bytes.Buffer{}
err := p.Render(buf)
if err != nil {
- Logger.Printf("%s %s path: %s while trying 5xx: %s",
- r.RemoteAddr,
- r.Method,
- u,
- err)
+ logErr(r, "while trying 5xx", err)
http.Error(w, "Internal server error", statusCode)
return
@@ -51,32 +29,20 @@ func Render5xx(w http.ResponseWriter, r *http.Request,
_, _ = w.Write(buf.Bytes())
- Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u)
+ logReq(r, statusCode)
}
// Render4xx is mostly used to render a 404 page, pulls from 404 template.
// automatically called by Render if a page is missing.
func Render4xx(w http.ResponseWriter, r *http.Request,
vars map[string]interface{}, statusCode int) {
- u := getURLPath(r)
-
- Logger.Printf("%s %s %d %s",
- r.RemoteAddr,
- r.Method,
- statusCode,
- u)
-
p := NewPage(TemplateDirectory + "/4xx")
buf := &bytes.Buffer{}
err := p.Render(buf)
if err != nil {
- Logger.Printf("%s %s path: %s while trying 404: %s",
- r.RemoteAddr,
- r.Method,
- u,
- err)
+ logErr(r, "while trying 404", err)
Render5xx(w, r, vars, http.StatusInternalServerError)
return
@@ -91,17 +57,13 @@ func Render4xx(w http.ResponseWriter, r *http.Request,
return
}
- Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u)
+ logReq(r, statusCode)
}
// Render is a lower level option, allowing you to specify local
// variables and the status code in which to return.
func Render(w http.ResponseWriter, r *http.Request,
path string, vars map[string]interface{}, statusCode int) {
- u := getURLPath(r)
-
- u = filepath.Join(".", u)
-
// Sepcifically use the specified path for the page
p := NewPage(path)
@@ -119,11 +81,7 @@ func Render(w http.ResponseWriter, r *http.Request,
return
}
- Logger.Printf("%s %s path: %s rendering encountered: %s",
- r.RemoteAddr,
- r.Method,
- u,
- err)
+ logErr(r, "rendering encountered", err)
Render5xx(w, r, vars, http.StatusInternalServerError)
return
@@ -133,18 +91,13 @@ func Render(w http.ResponseWriter, r *http.Request,
_, err = w.Write(buf.Bytes())
if err != nil {
- Logger.Printf("%s %s %d %s: while writing buf: %s",
- r.RemoteAddr,
- r.Method,
- statusCode,
- u,
- err)
+ logErr(r, "while writing to buf", err)
Render5xx(w, r, nil, http.StatusInternalServerError)
return
}
- Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u)
+ logReq(r, statusCode)
}
// RenderWithVars allows you to specify a specific page and whether or not
diff --git a/page/renderJson.go b/page/renderJson.go
index 9359b69..4c03e12 100644
--- a/page/renderJson.go
+++ b/page/renderJson.go
@@ -6,32 +6,28 @@ import (
"errors"
"io/fs"
"net/http"
- "path/filepath"
)
-// RenderJson is analogous to Render, though it renders the page,
+// RenderJSON is analogous to Render, though it renders the page,
// as a JSON key "content", optionally from the YAML it'll include
// additional keys in the json if specified under the 'pageJson' key.
// E.g.
-// ---
-// title: Some page
-// description: Some page desc
-// jsonvars:
-// - includeMeInJSON
-// vars:
-// notIncluded: some value
-// includeMeInJSON:
-// some: arbitrary
-// data: to
-// send: down
-// |--
-// My page content, wee!
-func RenderJson(w http.ResponseWriter, r *http.Request,
+//
+// ---
+// title: Some page
+// description: Some page desc
+// jsonvars:
+// - includeMeInJSON
+// vars:
+// notIncluded: some value
+// includeMeInJSON:
+// some: arbitrary
+// data: to
+// send: down
+// |--
+// My page content, wee!
+func RenderJSON(w http.ResponseWriter, r *http.Request, //nolint:funlen
path string, vars map[string]interface{}, statusCode int) {
- u := getURLPath(r)
-
- u = filepath.Join(".", u)
-
// Sepcifically use the specified path for the page
p := NewPage(path)
@@ -40,22 +36,18 @@ func RenderJson(w http.ResponseWriter, r *http.Request,
}
out := map[string]interface{}{}
-
buf := &bytes.Buffer{}
+
err := p.Render(buf)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
- renderJsonErr(w, r, "Not found", http.StatusNotFound)
+ renderJSONErr(w, "Not found", http.StatusNotFound)
return
}
- Logger.Printf("%s %s path: %s rendering encountered: %s",
- r.RemoteAddr,
- r.Method,
- u,
- err)
- renderJsonErr(w, r, "Internal server error",
+ logErr(r, "rendering JSON encountered", err)
+ renderJSONErr(w, "Internal server error",
http.StatusInternalServerError)
return
@@ -66,13 +58,7 @@ func RenderJson(w http.ResponseWriter, r *http.Request,
}
if r.URL.Query().Get("markdown") == "1" {
- if p.Vars != nil {
- p.Vars["RenderingMarkdownOnly"] = true
- } else {
- p.Vars = map[string]interface{}{
- "RenderingMarkdownOnly": true,
- }
- }
+ p.setRenderingMarkdownOnly()
// Tossing the error, since it would've been revealed above
md, _ := p.GetMarkdown()
out["Markdown"] = md
@@ -80,7 +66,7 @@ func RenderJson(w http.ResponseWriter, r *http.Request,
// Make a "set" of keys
keys := map[string]struct{}{}
- for _, k := range p.JsonVars {
+ for _, k := range p.JSONVars {
keys[k] = struct{}{}
}
@@ -100,26 +86,23 @@ func RenderJson(w http.ResponseWriter, r *http.Request,
err = enc.Encode(out)
if err != nil {
- Logger.Printf("%s %s %d %s: while writing buf: %s",
- r.RemoteAddr,
- r.Method,
- statusCode,
- u,
- err)
+ logErr(r, "while writing to buf", err)
return
}
- Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u)
+ logReq(r, statusCode)
}
-func renderJsonErr(w http.ResponseWriter, r *http.Request, msg string,
- statusCode int) {
+func renderJSONErr(w http.ResponseWriter, msg string, statusCode int) {
enc := json.NewEncoder(w)
w.WriteHeader(statusCode)
+
_ = enc.Encode(&struct {
- Status string
+ StatusCode int
+ Msg string
}{
- Status: msg,
+ StatusCode: statusCode,
+ Msg: msg,
})
}
diff --git a/page/renderMarkdown.go b/page/renderMarkdown.go
index 7a365e6..8356e55 100644
--- a/page/renderMarkdown.go
+++ b/page/renderMarkdown.go
@@ -5,19 +5,14 @@ import (
"errors"
"io/fs"
"net/http"
- "path/filepath"
)
// RenderMarkdown is analogous to Render, except it spits out rendered markdown
// as text/plain. It also sets .Vars.RenderingMarkdownOnly so templates can
// vary on whether or not they're plain markdown. For instance, not including
-// some HTML tags
+// some HTML tags.
func RenderMarkdown(w http.ResponseWriter, r *http.Request,
path string, vars map[string]interface{}, statusCode int) {
- u := getURLPath(r)
-
- u = filepath.Join(".", u)
-
// Sepcifically use the specified path for the page
p := NewPage(path)
@@ -38,21 +33,13 @@ func RenderMarkdown(w http.ResponseWriter, r *http.Request,
err := p.Render(buf)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
- w.WriteHeader(http.StatusNotFound)
- w.Header().Set("Content-type", "text/plain")
- w.Write([]byte("Not found"))
+ plainResp(w, http.StatusNotFound, "Not found")
return
}
- Logger.Printf("%s %s path: %s rendering encountered: %s",
- r.RemoteAddr,
- r.Method,
- u,
- err)
- w.WriteHeader(http.StatusInternalServerError)
- w.Header().Set("Content-type", "text/plain")
- w.Write([]byte("Internal server error"))
+ logErr(r, "while rendering", err)
+ plainResp(w, http.StatusInternalServerError, "Internal server error")
return
}
@@ -60,20 +47,15 @@ func RenderMarkdown(w http.ResponseWriter, r *http.Request,
// Error was handled above
md, _ := p.GetMarkdown()
- w.WriteHeader(statusCode)
w.Header().Set("Content-type", "text/plain")
+ w.WriteHeader(statusCode)
_, err = w.Write([]byte(md))
if err != nil {
- Logger.Printf("%s %s %d %s: while writing buf: %s",
- r.RemoteAddr,
- r.Method,
- statusCode,
- u,
- err)
+ logErr(r, "while writing buf", err)
return
}
- Logger.Printf("%s %s %d %s", r.RemoteAddr, r.Method, statusCode, u)
+ logReq(r, statusCode)
}
diff --git a/rediscache/main.go b/rediscache/main.go
index b504a24..0135534 100644
--- a/rediscache/main.go
+++ b/rediscache/main.go
@@ -68,7 +68,7 @@ func (rw *redisHTTPResponseWriter) Write(msg []byte) (int, error) {
rw.buf = &bytes.Buffer{}
}
- return rw.buf.Write(msg)
+ return rw.buf.Write(msg) //nolint:wrapcheck
}
// Simply for satisfying the http.ResponseWriter interface.