Compare commits
No commits in common. "576359798f3159032936b5cf6c727e2f38d5086a" and "8220922cc9a3f62c6d31bb16d835b3edaf4cfb60" have entirely different histories.
576359798f
...
8220922cc9
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@ -22,12 +22,10 @@ jobs:
|
|||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v4
|
||||||
- name: Build and push api
|
- name: Build and push api
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v5
|
||||||
env:
|
env:
|
||||||
ACTIONS_RUNTIME_TOKEN: ''
|
ACTIONS_RUNTIME_TOKEN: ''
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/arm64
|
|
||||||
provenance: false
|
|
||||||
context: .
|
context: .
|
||||||
push: true
|
push: true
|
||||||
tags: git.lovepin.app/nisan/avatars:latest
|
tags: git.lovepin.app/nisan/avatars:latest
|
||||||
@ -21,7 +21,7 @@ RUN go mod download
|
|||||||
RUN go mod verify
|
RUN go mod verify
|
||||||
|
|
||||||
# Build the binary.
|
# 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
|
# STEP 2 build a small image
|
||||||
|
|||||||
94
main.go
94
main.go
@ -11,7 +11,6 @@ import (
|
|||||||
"io/fs"
|
"io/fs"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
@ -20,54 +19,6 @@ import (
|
|||||||
"golang.org/x/image/draw"
|
"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/*
|
//go:embed assets/*
|
||||||
var files embed.FS
|
var files embed.FS
|
||||||
|
|
||||||
@ -101,31 +52,15 @@ func serveAvatar(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
// get the id and size from the request
|
// get the id and size from the request
|
||||||
id := r.PathValue("id")
|
id := r.PathValue("id")
|
||||||
sizeStr := r.PathValue("size")
|
size, err := strconv.Atoi(r.PathValue("size"))
|
||||||
if sizeStr == "" {
|
|
||||||
sizeStr = r.URL.Query().Get("s")
|
|
||||||
}
|
|
||||||
size, err := strconv.Atoi(sizeStr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
size = 400
|
size = 400
|
||||||
}
|
}
|
||||||
// min size is 32, max size is 2048
|
// min size is 32, max size is 1000
|
||||||
if size < 32 {
|
if size < 32 {
|
||||||
size = 32
|
size = 32
|
||||||
} else if size > 2048 {
|
} else if size > 1000 {
|
||||||
size = 2048
|
size = 1000
|
||||||
}
|
|
||||||
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)
|
img := image.NewRGBA(emptyRect)
|
||||||
@ -139,38 +74,26 @@ func serveAvatar(w http.ResponseWriter, r *http.Request) {
|
|||||||
if size != 400 {
|
if size != 400 {
|
||||||
// resize the image
|
// resize the image
|
||||||
dst := image.NewRGBA(image.Rect(0, 0, size, size))
|
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
|
img = dst
|
||||||
}
|
}
|
||||||
|
|
||||||
// write the image to the response
|
// write the image to the response
|
||||||
w.Header().Set("content-type", "image/jpeg")
|
w.Header().Set("content-type", "image/jpeg")
|
||||||
w.WriteHeader(http.StatusCreated)
|
w.WriteHeader(http.StatusCreated)
|
||||||
buf := new(bytes.Buffer)
|
err = jpeg.Encode(w, img, nil)
|
||||||
err = jpeg.Encode(buf, img, nil)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err.Error())
|
log.Println(err.Error())
|
||||||
}
|
}
|
||||||
byteSize, err := w.Write(buf.Bytes())
|
log.Printf("[id:%s][size:%d] served to [%s] [%s] [%v]\n", id, size, r.RemoteAddr, r.Proto, time.Since(startTime))
|
||||||
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() {
|
func main() {
|
||||||
go func() {
|
log.Println("assets loaded")
|
||||||
for range time.Tick(time.Minute) {
|
|
||||||
avatarCache.Clear()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc("GET /{id}", serveAvatar)
|
mux.HandleFunc("GET /{id}", serveAvatar)
|
||||||
mux.HandleFunc("GET /{size}/{id}", serveAvatar)
|
mux.HandleFunc("GET /{size}/{id}", serveAvatar)
|
||||||
http.ListenAndServe("0.0.0.0:3382", mux)
|
http.ListenAndServe("0.0.0.0:3382", mux)
|
||||||
log.Println("Server started at 0.0.0.0:3382")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -217,5 +140,4 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Printf("%d different combinations loaded\n", len(BaseRects)*len(EYES)*len(NOSE)*len(MOUTH))
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user