Compare commits

..

No commits in common. "576359798f3159032936b5cf6c727e2f38d5086a" and "8220922cc9a3f62c6d31bb16d835b3edaf4cfb60" have entirely different histories.

3 changed files with 10 additions and 90 deletions

View File

@ -22,12 +22,10 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
- name: Build and push api
uses: docker/build-push-action@v6
uses: docker/build-push-action@v5
env:
ACTIONS_RUNTIME_TOKEN: ''
with:
platforms: linux/amd64,linux/arm64
provenance: false
context: .
push: true
tags: git.lovepin.app/nisan/avatars:latest

View File

@ -21,7 +21,7 @@ RUN go mod download
RUN go mod verify
# Build the binary.
RUN CGO_ENABLED=0 go build -ldflags="-w -s" -o /go/bin/avatars
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="-w -s" -o /go/bin/avatars
############################
# STEP 2 build a small image

94
main.go
View File

@ -11,7 +11,6 @@ import (
"io/fs"
"log"
"net/http"
"sync"
"strconv"
"strings"
@ -20,54 +19,6 @@ 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
@ -101,31 +52,15 @@ func serveAvatar(w http.ResponseWriter, r *http.Request) {
// get the id and size from the request
id := r.PathValue("id")
sizeStr := r.PathValue("size")
if sizeStr == "" {
sizeStr = r.URL.Query().Get("s")
}
size, err := strconv.Atoi(sizeStr)
size, err := strconv.Atoi(r.PathValue("size"))
if err != nil {
size = 400
}
// min size is 32, max size is 2048
// min size is 32, max size is 1000
if size < 32 {
size = 32
} 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
} else if size > 1000 {
size = 1000
}
img := image.NewRGBA(emptyRect)
@ -139,38 +74,26 @@ func serveAvatar(w http.ResponseWriter, r *http.Request) {
if size != 400 {
// resize the image
dst := image.NewRGBA(image.Rect(0, 0, size, size))
draw.CatmullRom.Scale(dst, dst.Bounds(), img, emptyRect, draw.Over, nil)
draw.ApproxBiLinear.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)
buf := new(bytes.Buffer)
err = jpeg.Encode(buf, img, nil)
err = jpeg.Encode(w, img, nil)
if err != nil {
log.Println(err.Error())
}
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))
log.Printf("[id:%s][size:%d] served to [%s] [%s] [%v]\n", id, size, r.RemoteAddr, r.Proto, time.Since(startTime))
}
func main() {
go func() {
for range time.Tick(time.Minute) {
avatarCache.Clear()
}
}()
log.Println("assets loaded")
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() {
@ -217,5 +140,4 @@ func init() {
}
}
}
log.Printf("%d different combinations loaded\n", len(BaseRects)*len(EYES)*len(NOSE)*len(MOUTH))
}