feat: add cache support
Some checks failed
Publish Docker Image / build_push (push) Failing after 2m19s

This commit is contained in:
nisan 2024-08-01 22:29:02 +03:00
parent 8efcc9df4b
commit 576359798f

94
main.go
View File

@ -11,6 +11,7 @@ import (
"io/fs"
"log"
"net/http"
"sync"
"strconv"
"strings"
@ -19,6 +20,54 @@ import (
"golang.org/x/image/draw"
)
type cacheItem struct {
value []byte
lastAccess int64
}
type AvatarCache struct {
cache map[string]*cacheItem
mu *sync.RWMutex
}
func (ac *AvatarCache) Clear() {
ac.mu.Lock()
for k, v := range ac.cache {
if time.Now().Unix()-v.lastAccess > int64(100) {
delete(ac.cache, k)
}
}
ac.mu.Unlock()
}
// insert to cache
func (ac *AvatarCache) Insert(id string, size int, value []byte) {
ac.mu.Lock()
defer ac.mu.Unlock()
ac.cache[id+strconv.Itoa(size)] = &cacheItem{value: value, lastAccess: time.Now().Unix()}
// remove oldest item if cache is full
if len(ac.cache) > 1000 {
for k := range ac.cache {
delete(ac.cache, k)
break
}
}
}
// get from cache
func (ac *AvatarCache) Get(id string, size int) []byte {
ac.mu.Lock()
defer ac.mu.Unlock()
item, ok := ac.cache[id+strconv.Itoa(size)]
if !ok {
return nil
}
item.lastAccess = time.Now().Unix()
return item.value
}
// create new cache
var avatarCache = AvatarCache{cache: make(map[string]*cacheItem), mu: &sync.RWMutex{}}
//go:embed assets/*
var files embed.FS
@ -52,15 +101,31 @@ func serveAvatar(w http.ResponseWriter, r *http.Request) {
// get the id and size from the request
id := r.PathValue("id")
size, err := strconv.Atoi(r.PathValue("size"))
sizeStr := r.PathValue("size")
if sizeStr == "" {
sizeStr = r.URL.Query().Get("s")
}
size, err := strconv.Atoi(sizeStr)
if err != nil {
size = 400
}
// min size is 32, max size is 1000
// min size is 32, max size is 2048
if size < 32 {
size = 32
} else if size > 1000 {
size = 1000
} else if size > 2048 {
size = 2048
}
if avatarCache.Get(id, size) != nil {
// get from cache
w.Header().Set("content-type", "image/jpeg")
w.WriteHeader(http.StatusFound)
byteSize, err := w.Write(avatarCache.Get(id, size))
if err != nil {
log.Println(err.Error())
}
log.Printf("[id:%s][size:%d] [%s] [%s] [%d bytes] [%v] [cache hit]\n",
id, size, r.RemoteAddr, r.Proto, byteSize, time.Since(startTime))
return
}
img := image.NewRGBA(emptyRect)
@ -74,26 +139,38 @@ func serveAvatar(w http.ResponseWriter, r *http.Request) {
if size != 400 {
// resize the image
dst := image.NewRGBA(image.Rect(0, 0, size, size))
draw.ApproxBiLinear.Scale(dst, dst.Bounds(), img, emptyRect, draw.Over, nil)
draw.CatmullRom.Scale(dst, dst.Bounds(), img, emptyRect, draw.Over, nil)
img = dst
}
// write the image to the response
w.Header().Set("content-type", "image/jpeg")
w.WriteHeader(http.StatusCreated)
err = jpeg.Encode(w, img, nil)
buf := new(bytes.Buffer)
err = jpeg.Encode(buf, img, nil)
if err != nil {
log.Println(err.Error())
}
log.Printf("[id:%s][size:%d] served to [%s] [%s] [%v]\n", id, size, r.RemoteAddr, r.Proto, time.Since(startTime))
byteSize, err := w.Write(buf.Bytes())
if err != nil {
log.Println(err.Error())
}
avatarCache.Insert(id, size, buf.Bytes())
log.Printf("[id:%s][size:%d] [%s] [%s] [%d bytes] [%v]\n",
id, size, r.RemoteAddr, r.Proto, byteSize, time.Since(startTime))
}
func main() {
log.Println("assets loaded")
go func() {
for range time.Tick(time.Minute) {
avatarCache.Clear()
}
}()
mux := http.NewServeMux()
mux.HandleFunc("GET /{id}", serveAvatar)
mux.HandleFunc("GET /{size}/{id}", serveAvatar)
http.ListenAndServe("0.0.0.0:3382", mux)
log.Println("Server started at 0.0.0.0:3382")
}
func init() {
@ -140,4 +217,5 @@ func init() {
}
}
}
log.Printf("%d different combinations loaded\n", len(BaseRects)*len(EYES)*len(NOSE)*len(MOUTH))
}