Implement basic db-based cache

This commit is contained in:
Jan-Lukas Else 2020-07-29 22:45:26 +02:00
parent 6c4a9476b4
commit 7becbd6aad
5 changed files with 85 additions and 4 deletions

53
cache.go Normal file
View File

@ -0,0 +1,53 @@
package main
import (
"context"
"net/http"
"net/url"
"time"
)
func CacheMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestUrl, _ := url.ParseRequestURI(r.RequestURI)
if appConfig.cache.enable &&
// Check bypass query
!(requestUrl != nil && requestUrl.Query().Get("cache") == "0") {
mime, t, cache := getCache(SlashTrimmedPath(r), r.Context())
if cache == nil {
next.ServeHTTP(w, r)
return
} else {
expiresTime := time.Unix(t+appConfig.cache.expiration, 0).Format(time.RFC1123)
w.Header().Set("Expires", expiresTime)
w.Header().Set("Content-Type", mime)
_, _ = w.Write(cache)
}
} else {
next.ServeHTTP(w, r)
}
})
}
func getCache(path string, context context.Context) (string, int64, []byte) {
var mime string
var t int64
var cache []byte
allowedTime := time.Now().Unix() - appConfig.cache.expiration
row := appDb.QueryRowContext(context, "select COALESCE(mime, ''), COALESCE(time, 0), value from cache where path=? and time>=?", path, allowedTime)
_ = row.Scan(&mime, &t, &cache)
return mime, t, cache
}
func saveCache(path string, mime string, value []byte) {
now := time.Now().Unix()
startWritingToDb()
tx, err := appDb.Begin()
if err != nil {
return
}
_, _ = tx.Exec("delete from cache where time<?;", now-appConfig.cache.expiration)
_, _ = tx.Exec("insert or replace into cache (path, time, mime, value) values (?, ?, ?, ?);", path, now, mime, value)
_ = tx.Commit()
finishWritingToDb()
}

View File

@ -8,6 +8,7 @@ import (
type config struct {
server *configServer
db *configDb
cache *configCache
}
type configServer struct {
@ -19,9 +20,15 @@ type configDb struct {
file string
}
type configCache struct {
enable bool
expiration int64
}
var appConfig = &config{
server: &configServer{},
db: &configDb{},
cache: &configCache{},
}
func initConfig() error {
@ -45,6 +52,15 @@ func initConfig() error {
viper.SetDefault(databaseFile, "data/db.sqlite")
appConfig.db.file = viper.GetString(databaseFile)
logConfig(databaseFile, appConfig.db.file)
// Caching
cacheEnable := "cache.enable"
viper.SetDefault(cacheEnable, true)
appConfig.cache.enable = viper.GetBool(cacheEnable)
logConfig(cacheEnable, appConfig.cache.enable)
cacheExpiration := "cache.expiration"
viper.SetDefault(cacheExpiration, 600)
appConfig.cache.expiration = viper.GetInt64(cacheExpiration)
logConfig(cacheExpiration, appConfig.cache.expiration)
return nil
}

View File

@ -31,6 +31,13 @@ func migrateDb() error {
return err
},
},
&migrator.Migration{
Name: "00004",
Func: func(tx *sql.Tx) error {
_, err := tx.Exec("create table cache (path text not null primary key, time integer, mime text, value blob);")
return err
},
},
),
)
if err != nil {

View File

@ -63,7 +63,7 @@ func buildHandler() (http.Handler, error) {
} else {
for _, path := range allPostPaths {
if path != "" {
r.Get(path, servePost)
r.With(CacheMiddleware).Get(path, servePost)
}
}
}
@ -109,4 +109,4 @@ func SlashTrimmedPath(r *http.Request) string {
path = strings.TrimSuffix(path, "/")
}
return path
}
}

View File

@ -18,7 +18,8 @@ type post struct {
}
func servePost(w http.ResponseWriter, r *http.Request) {
post, err := getPost(SlashTrimmedPath(r), r.Context())
path := SlashTrimmedPath(r)
post, err := getPost(path, r.Context())
if err == postNotFound {
http.NotFound(w, r)
return
@ -31,7 +32,11 @@ func servePost(w http.ResponseWriter, r *http.Request) {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "text/html")
mime := "text/html"
if appConfig.cache.enable {
saveCache(path, mime, htmlContent)
}
w.Header().Set("Content-Type", mime)
_, _ = w.Write(htmlContent)
}