Major code cleanup and switch to Fiber

This commit is contained in:
Jacob Gunther
2022-12-16 02:18:37 -06:00
parent 88202db62c
commit 23ac7fe894
16 changed files with 627 additions and 1370 deletions

View File

@@ -1,7 +1,7 @@
package conf
package main
import (
"io/ioutil"
"os"
"time"
"gopkg.in/yaml.v3"
@@ -17,6 +17,8 @@ type RouteConfig struct {
}
type Configuration struct {
Host string `yaml:"host"`
Port uint16 `yaml:"port"`
Redis struct {
URI string `yaml:"uri"`
Database int `yaml:"database"`
@@ -39,7 +41,7 @@ type Configuration struct {
}
func (c *Configuration) ReadFile(file string) error {
data, err := ioutil.ReadFile(file)
data, err := os.ReadFile(file)
if err != nil {
return err

View File

@@ -3,24 +3,25 @@ package main
import (
"fmt"
"log"
"os"
"strconv"
"time"
"net/http"
"github.com/buaazp/fasthttprouter"
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/cors"
"github.com/gofiber/fiber/v2/middleware/recover"
"github.com/joho/godotenv"
"github.com/mineatar-io/api-server/src/conf"
"github.com/mineatar-io/api-server/src/redis"
"github.com/mineatar-io/api-server/src/routes"
"github.com/mineatar-io/api-server/src/util"
"github.com/valyala/fasthttp"
)
var (
host string = "127.0.0.1"
port uint16 = 3000
config *conf.Configuration = &conf.Configuration{}
r *redis.Redis = &redis.Redis{}
app *fiber.App = fiber.New(fiber.Config{
DisableStartupMessage: true,
ErrorHandler: func(ctx *fiber.Ctx, err error) error {
log.Println(ctx.Request().URI(), err)
return ctx.SendStatus(http.StatusInternalServerError)
},
})
r *Redis = &Redis{}
config *Configuration = &Configuration{}
)
func init() {
@@ -32,64 +33,29 @@ func init() {
log.Fatal(err)
}
start := time.Now()
if err = r.Connect(config.Redis.URI, config.Redis.Database); err != nil {
log.Fatal(err)
}
log.Printf("Successfully connected to Redis (%s)\n", time.Since(start))
log.Println("Successfully connected to Redis")
if value, ok := os.LookupEnv("HOST"); ok {
host = value
}
if value, ok := os.LookupEnv("PORT"); ok {
parsedValue, err := strconv.ParseUint(value, 10, 16)
if err != nil {
log.Fatal(err)
}
port = uint16(parsedValue)
}
routes.Init(r, config)
util.Init(r, config)
}
func middleware(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
ctx.Response.Header.Set("Access-Control-Allow-Origin", "*")
ctx.Response.Header.Set("Access-Control-Allow-Headers", "*")
ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET,POST,HEAD,OPTIONS")
ctx.Response.Header.Set("Access-Control-Expose-Headers", "X-Cache-Hit")
if util.Debug {
log.Printf("%s %s (%s) - %s\n", ctx.Method(), ctx.URI(), ctx.RemoteAddr(), ctx.UserAgent())
}
next(ctx)
}
app.Use(recover.New())
app.Use(cors.New(cors.Config{
AllowOrigins: "*",
AllowMethods: "HEAD,OPTIONS,GET",
ExposeHeaders: "Content-Type,Content-Disposition,X-Cache-Hit",
}))
}
func main() {
defer r.Close()
router := fasthttprouter.New()
instanceID, err := GetInstanceID()
router.GET("/ping", routes.PingHandler)
router.GET("/uuid/:user", routes.UUIDHandler)
router.GET("/skin/:user", routes.SkinHandler)
router.GET("/face/:user", routes.FaceHandler)
router.GET("/head/:user", routes.HeadHandler)
router.GET("/body/full/:user", routes.FullBodyHandler)
router.GET("/body/front/:user", routes.FrontBodyHandler)
router.GET("/body/back/:user", routes.BackBodyHandler)
router.GET("/body/left/:user", routes.LeftBodyHandler)
router.GET("/body/right/:user", routes.RightBodyHandler)
if err != nil {
log.Fatal(err)
}
log.Printf("Listening on %s:%d\n", host, port)
log.Fatal(fasthttp.ListenAndServe(fmt.Sprintf("%s:%d", host, port), middleware(router.Handler)))
log.Printf("Listening on %s:%d\n", config.Host, config.Port+instanceID)
log.Fatal(app.Listen(fmt.Sprintf("%s:%d", config.Host, config.Port+instanceID)))
}

View File

@@ -1,4 +1,4 @@
package redis
package main
import (
"bytes"
@@ -8,27 +8,6 @@ import (
"time"
"github.com/go-redis/redis/v8"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
redisGetMetric = promauto.NewCounter(prometheus.CounterOpts{
Name: "redis_get_count",
Help: "The amount of Redis GET requests",
})
redisSetMetric = promauto.NewCounter(prometheus.CounterOpts{
Name: "redis_set_count",
Help: "The amount of Redis SET requests",
})
redisExistsMetric = promauto.NewCounter(prometheus.CounterOpts{
Name: "redis_exists_count",
Help: "The amount of Redis EXIST requests",
})
redisDeleteMetric = promauto.NewCounter(prometheus.CounterOpts{
Name: "redis_delete_count",
Help: "The amount of Redis DELETE requests",
})
)
type Redis struct {
@@ -51,8 +30,6 @@ func (r *Redis) Connect(uri string, database int) error {
}
func (r *Redis) GetString(key string) (string, bool, error) {
redisGetMetric.Inc()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
@@ -73,8 +50,6 @@ func (r *Redis) GetString(key string) (string, bool, error) {
}
func (r *Redis) GetBytes(key string) ([]byte, bool, error) {
redisGetMetric.Inc()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
@@ -101,8 +76,6 @@ func (r *Redis) GetBytes(key string) ([]byte, bool, error) {
}
func (r *Redis) GetNRGBA(key string) (*image.NRGBA, bool, error) {
redisGetMetric.Inc()
value, ok, err := r.GetBytes(key)
if err != nil {
@@ -131,8 +104,6 @@ func (r *Redis) GetNRGBA(key string) (*image.NRGBA, bool, error) {
}
func (r *Redis) Exists(key string) (bool, error) {
redisExistsMetric.Inc()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
@@ -143,8 +114,6 @@ func (r *Redis) Exists(key string) (bool, error) {
}
func (r *Redis) Delete(key string) error {
redisDeleteMetric.Inc()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
@@ -153,8 +122,6 @@ func (r *Redis) Delete(key string) error {
}
func (r *Redis) Set(key string, value interface{}, ttl time.Duration) error {
redisSetMetric.Inc()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

543
src/routes.go Normal file
View File

@@ -0,0 +1,543 @@
package main
import (
"fmt"
"net/http"
"github.com/gofiber/fiber/v2"
"github.com/mineatar-io/skin-render"
)
func init() {
app.Get("/ping", PingHandler)
app.Get("/uuid/:user", UUIDHandler)
app.Get("/skin/:user", SkinHandler)
app.Get("/face/:user", FaceHandler)
app.Get("/head/:user", HeadHandler)
app.Get("/body/full/:user", FullBodyHandler)
app.Get("/body/front/:user", FrontBodyHandler)
app.Get("/body/back/:user", BackBodyHandler)
app.Get("/body/left/:user", LeftBodyHandler)
app.Get("/body/right/:user", RightBodyHandler)
app.Use(NotFoundHandler)
}
func PingHandler(ctx *fiber.Ctx) error {
return ctx.SendString("Pong!")
}
func FullBodyHandler(ctx *fiber.Ctx) error {
user := ctx.Params("user")
opts := ParseQueryParams(ctx, config.Routes.FullBody)
uuid, ok, err := LookupUUID(user)
if err != nil {
return err
}
if !ok && !opts.Fallback {
return ctx.SendStatus(http.StatusNotFound)
}
cacheKey := fmt.Sprintf("result:fullbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
return err
}
if ok {
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "TRUE")
return ctx.Type("png").Send(cache)
}
}
rawSkin, slim, err := GetPlayerSkin(uuid)
if err != nil {
return err
}
render := skin.RenderBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
data, err := EncodePNG(render)
if err != nil {
return err
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
return err
}
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "FALSE")
return ctx.Type("png").Send(data)
}
func FrontBodyHandler(ctx *fiber.Ctx) error {
user := ctx.Params("user")
opts := ParseQueryParams(ctx, config.Routes.FrontBody)
uuid, ok, err := LookupUUID(user)
if err != nil {
return err
}
if !ok && !opts.Fallback {
return ctx.SendStatus(http.StatusNotFound)
}
cacheKey := fmt.Sprintf("result:frontbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
return err
}
if ok {
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "TRUE")
return ctx.Type("png").Send(cache)
}
}
rawSkin, slim, err := GetPlayerSkin(uuid)
if err != nil {
return err
}
render := skin.RenderFrontBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
data, err := EncodePNG(render)
if err != nil {
return err
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
return err
}
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "FALSE")
return ctx.Type("png").Send(data)
}
func BackBodyHandler(ctx *fiber.Ctx) error {
user := ctx.Params("user")
opts := ParseQueryParams(ctx, config.Routes.BackBody)
uuid, ok, err := LookupUUID(user)
if err != nil {
return err
}
if !ok && !opts.Fallback {
return ctx.SendStatus(http.StatusNotFound)
}
cacheKey := fmt.Sprintf("result:backbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
return err
}
if ok {
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "TRUE")
return ctx.Type("png").Send(cache)
}
}
rawSkin, slim, err := GetPlayerSkin(uuid)
if err != nil {
return err
}
render := skin.RenderBackBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
data, err := EncodePNG(render)
if err != nil {
return err
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
return err
}
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "FALSE")
return ctx.Type("png").Send(data)
}
func LeftBodyHandler(ctx *fiber.Ctx) error {
user := ctx.Params("user")
opts := ParseQueryParams(ctx, config.Routes.LeftBody)
uuid, ok, err := LookupUUID(user)
if err != nil {
return err
}
if !ok && !opts.Fallback {
return ctx.SendStatus(http.StatusNotFound)
}
cacheKey := fmt.Sprintf("result:leftbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
return err
}
if ok {
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "TRUE")
return ctx.Type("png").Send(cache)
}
}
rawSkin, slim, err := GetPlayerSkin(uuid)
if err != nil {
return err
}
render := skin.RenderLeftBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
data, err := EncodePNG(render)
if err != nil {
return err
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
return err
}
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "FALSE")
return ctx.Type("png").Send(data)
}
func RightBodyHandler(ctx *fiber.Ctx) error {
user := ctx.Params("user")
opts := ParseQueryParams(ctx, config.Routes.RightBody)
uuid, ok, err := LookupUUID(user)
if err != nil {
return err
}
if !ok && !opts.Fallback {
return ctx.SendStatus(http.StatusNotFound)
}
cacheKey := fmt.Sprintf("result:rightbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
return err
}
if ok {
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "TRUE")
return ctx.Type("png").Send(cache)
}
}
rawSkin, slim, err := GetPlayerSkin(uuid)
if err != nil {
return err
}
render := skin.RenderRightBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
data, err := EncodePNG(render)
if err != nil {
return err
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
return err
}
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "FALSE")
return ctx.Type("png").Send(data)
}
func FaceHandler(ctx *fiber.Ctx) error {
user := ctx.Params("user")
opts := ParseQueryParams(ctx, config.Routes.Face)
uuid, ok, err := LookupUUID(user)
if err != nil {
return err
}
if !ok && !opts.Fallback {
return ctx.SendStatus(http.StatusNotFound)
}
cacheKey := fmt.Sprintf("result:face-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
return err
}
if ok {
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "TRUE")
return ctx.Type("png").Send(cache)
}
}
rawSkin, slim, err := GetPlayerSkin(uuid)
if err != nil {
return err
}
render := skin.RenderFace(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
data, err := EncodePNG(render)
if err != nil {
return err
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
return err
}
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "FALSE")
return ctx.Type("png").Send(data)
}
func HeadHandler(ctx *fiber.Ctx) error {
user := ctx.Params("user")
opts := ParseQueryParams(ctx, config.Routes.Head)
uuid, ok, err := LookupUUID(user)
if err != nil {
return err
}
if !ok && !opts.Fallback {
return ctx.SendStatus(http.StatusNotFound)
}
cacheKey := fmt.Sprintf("result:head-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
return err
}
if ok {
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "TRUE")
return ctx.Type("png").Send(cache)
}
}
rawSkin, slim, err := GetPlayerSkin(uuid)
if err != nil {
return err
}
render := skin.RenderHead(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
data, err := EncodePNG(render)
if err != nil {
return err
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
return err
}
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Set("X-Cache-Hit", "FALSE")
return ctx.Type("png").Send(data)
}
func SkinHandler(ctx *fiber.Ctx) error {
user := ctx.Params("user")
opts := ParseQueryParams(ctx, config.Routes.RawSkin)
uuid, ok, err := LookupUUID(user)
if err != nil {
return err
}
if !ok && !opts.Fallback {
return ctx.SendStatus(http.StatusNotFound)
}
rawSkin, _, err := GetPlayerSkin(uuid)
if err != nil {
return err
}
data, err := EncodePNG(rawSkin)
if err != nil {
return err
}
if opts.Download {
ctx.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
return ctx.Type("png").Send(data)
}
func UUIDHandler(ctx *fiber.Ctx) error {
user := ctx.Params("user")
uuid, ok, err := LookupUUID(user)
if err != nil {
return err
}
if !ok {
return ctx.SendStatus(http.StatusNotFound)
}
return ctx.SendString(uuid)
}
func NotFoundHandler(ctx *fiber.Ctx) error {
return ctx.SendStatus(http.StatusNotFound)
}

View File

@@ -1,451 +0,0 @@
package routes
import (
"fmt"
"log"
"net/http"
"github.com/mineatar-io/api-server/src/util"
"github.com/mineatar-io/skin-render"
"github.com/valyala/fasthttp"
)
func FullBodyHandler(ctx *fasthttp.RequestCtx) {
user := ctx.UserValue("user").(string)
opts := util.ParseQueryParams(ctx, config.Routes.FullBody)
uuid, ok, err := util.LookupUUID(user)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if !ok && !opts.Fallback {
util.WriteError(ctx, nil, http.StatusNotFound)
return
}
cacheKey := fmt.Sprintf("result:fullbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if ok {
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
if util.Debug {
log.Printf("Retrieved cache for full body render for '%s'\n", uuid)
}
ctx.Response.Header.Set("X-Cache-Hit", "TRUE")
ctx.SetContentType("image/png")
ctx.SetBody(cache)
return
}
}
rawSkin, slim, err := util.GetPlayerSkin(uuid)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
render := skin.RenderBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
if util.Debug {
log.Printf("Rendered full body image for '%s'\n", uuid)
}
data, err := util.EncodePNG(render)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Response.Header.Set("X-Cache-Hit", "FALSE")
ctx.SetContentType("image/png")
ctx.SetBody(data)
}
func FrontBodyHandler(ctx *fasthttp.RequestCtx) {
user := ctx.UserValue("user").(string)
opts := util.ParseQueryParams(ctx, config.Routes.FrontBody)
uuid, ok, err := util.LookupUUID(user)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if !ok && !opts.Fallback {
util.WriteError(ctx, nil, http.StatusNotFound)
return
}
cacheKey := fmt.Sprintf("result:frontbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if ok {
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
if util.Debug {
log.Printf("Retrieved cache for front body render for '%s'\n", uuid)
}
ctx.Response.Header.Set("X-Cache-Hit", "TRUE")
ctx.SetContentType("image/png")
ctx.SetBody(cache)
return
}
}
rawSkin, slim, err := util.GetPlayerSkin(uuid)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
render := skin.RenderFrontBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
if util.Debug {
log.Printf("Rendered front body image for '%s'\n", uuid)
}
data, err := util.EncodePNG(render)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Response.Header.Set("X-Cache-Hit", "FALSE")
ctx.SetContentType("image/png")
ctx.SetBody(data)
}
func BackBodyHandler(ctx *fasthttp.RequestCtx) {
user := ctx.UserValue("user").(string)
opts := util.ParseQueryParams(ctx, config.Routes.BackBody)
uuid, ok, err := util.LookupUUID(user)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if !ok && !opts.Fallback {
util.WriteError(ctx, nil, http.StatusNotFound)
return
}
cacheKey := fmt.Sprintf("result:backbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if ok {
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
if util.Debug {
log.Printf("Retrieved cache for back body render for '%s'\n", uuid)
}
ctx.Response.Header.Set("X-Cache-Hit", "TRUE")
ctx.SetContentType("image/png")
ctx.SetBody(cache)
return
}
}
rawSkin, slim, err := util.GetPlayerSkin(uuid)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
render := skin.RenderBackBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
if util.Debug {
log.Printf("Rendered back body image for '%s'\n", uuid)
}
data, err := util.EncodePNG(render)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Response.Header.Set("X-Cache-Hit", "FALSE")
ctx.SetContentType("image/png")
ctx.SetBody(data)
}
func LeftBodyHandler(ctx *fasthttp.RequestCtx) {
user := ctx.UserValue("user").(string)
opts := util.ParseQueryParams(ctx, config.Routes.LeftBody)
uuid, ok, err := util.LookupUUID(user)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if !ok && !opts.Fallback {
util.WriteError(ctx, nil, http.StatusNotFound)
return
}
cacheKey := fmt.Sprintf("result:leftbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if ok {
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
if util.Debug {
log.Printf("Retrieved cache for left body render for '%s'\n", uuid)
}
ctx.Response.Header.Set("X-Cache-Hit", "TRUE")
ctx.SetContentType("image/png")
ctx.SetBody(cache)
return
}
}
rawSkin, slim, err := util.GetPlayerSkin(uuid)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
render := skin.RenderLeftBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
if util.Debug {
log.Printf("Rendered left body image for '%s'\n", uuid)
}
data, err := util.EncodePNG(render)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Response.Header.Set("X-Cache-Hit", "FALSE")
ctx.SetContentType("image/png")
ctx.SetBody(data)
}
func RightBodyHandler(ctx *fasthttp.RequestCtx) {
user := ctx.UserValue("user").(string)
opts := util.ParseQueryParams(ctx, config.Routes.RightBody)
uuid, ok, err := util.LookupUUID(user)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if !ok && !opts.Fallback {
util.WriteError(ctx, nil, http.StatusNotFound)
return
}
cacheKey := fmt.Sprintf("result:rightbody-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if ok {
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
if util.Debug {
log.Printf("Retrieved cache for right body render for '%s'\n", uuid)
}
ctx.Response.Header.Set("X-Cache-Hit", "TRUE")
ctx.SetContentType("image/png")
ctx.SetBody(cache)
return
}
}
rawSkin, slim, err := util.GetPlayerSkin(uuid)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
render := skin.RenderRightBody(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
if util.Debug {
log.Printf("Rendered right body image for '%s'\n", uuid)
}
data, err := util.EncodePNG(render)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Response.Header.Set("X-Cache-Hit", "FALSE")
ctx.SetContentType("image/png")
ctx.SetBody(data)
}

View File

@@ -1,99 +0,0 @@
package routes
import (
"fmt"
"log"
"net/http"
"github.com/mineatar-io/api-server/src/util"
"github.com/mineatar-io/skin-render"
"github.com/valyala/fasthttp"
)
func FaceHandler(ctx *fasthttp.RequestCtx) {
user := ctx.UserValue("user").(string)
opts := util.ParseQueryParams(ctx, config.Routes.Face)
uuid, ok, err := util.LookupUUID(user)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if !ok && !opts.Fallback {
util.WriteError(ctx, nil, http.StatusNotFound)
return
}
cacheKey := fmt.Sprintf("result:face-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if ok {
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
if util.Debug {
log.Printf("Retrieved cache for face render for '%s'\n", uuid)
}
ctx.Response.Header.Set("X-Cache-Hit", "TRUE")
ctx.SetContentType("image/png")
ctx.SetBody(cache)
return
}
}
rawSkin, slim, err := util.GetPlayerSkin(uuid)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
render := skin.RenderFace(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
if util.Debug {
log.Printf("Rendered face image for '%s'\n", uuid)
}
data, err := util.EncodePNG(render)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Response.Header.Set("X-Cache-Hit", "FALSE")
ctx.SetContentType("image/png")
ctx.SetBody(data)
}

View File

@@ -1,99 +0,0 @@
package routes
import (
"fmt"
"log"
"net/http"
"github.com/mineatar-io/api-server/src/util"
"github.com/mineatar-io/skin-render"
"github.com/valyala/fasthttp"
)
func HeadHandler(ctx *fasthttp.RequestCtx) {
user := ctx.UserValue("user").(string)
opts := util.ParseQueryParams(ctx, config.Routes.Head)
uuid, ok, err := util.LookupUUID(user)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if !ok && !opts.Fallback {
util.WriteError(ctx, nil, http.StatusNotFound)
return
}
cacheKey := fmt.Sprintf("result:head-%d-%t-%s", opts.Scale, opts.Overlay, uuid)
{
cache, ok, err := r.GetBytes(cacheKey)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if ok {
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
if util.Debug {
log.Printf("Retrieved cache for head render for '%s'\n", uuid)
}
ctx.Response.Header.Set("X-Cache-Hit", "TRUE")
ctx.SetContentType("image/png")
ctx.SetBody(cache)
return
}
}
rawSkin, slim, err := util.GetPlayerSkin(uuid)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
render := skin.RenderHead(rawSkin, skin.Options{
Overlay: opts.Overlay,
Slim: slim,
Scale: opts.Scale,
})
if util.Debug {
log.Printf("Rendered head image for '%s'\n", uuid)
}
data, err := util.EncodePNG(render)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if err = r.Set(cacheKey, data, config.Cache.RenderCacheDuration); err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.Response.Header.Set("X-Cache-Hit", "FALSE")
ctx.SetContentType("image/png")
ctx.SetBody(data)
}

View File

@@ -1,16 +0,0 @@
package routes
import (
"github.com/mineatar-io/api-server/src/conf"
"github.com/mineatar-io/api-server/src/redis"
)
var (
r *redis.Redis
config *conf.Configuration
)
func Init(red *redis.Redis, c *conf.Configuration) {
r = red
config = c
}

View File

@@ -1,9 +0,0 @@
package routes
import (
"github.com/valyala/fasthttp"
)
func PingHandler(ctx *fasthttp.RequestCtx) {
ctx.SetBodyString("Pong!")
}

View File

@@ -1,52 +0,0 @@
package routes
import (
"fmt"
"net/http"
"github.com/mineatar-io/api-server/src/util"
"github.com/valyala/fasthttp"
)
func SkinHandler(ctx *fasthttp.RequestCtx) {
user := ctx.UserValue("user").(string)
opts := util.ParseQueryParams(ctx, config.Routes.RawSkin)
uuid, ok, err := util.LookupUUID(user)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if !ok && !opts.Fallback {
util.WriteError(ctx, nil, http.StatusNotFound)
return
}
rawSkin, _, err := util.GetPlayerSkin(uuid)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
data, err := util.EncodePNG(rawSkin)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if opts.Download {
ctx.Response.Header.Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s.png"`, user))
}
ctx.SetContentType("image/png")
ctx.SetBody(data)
}

View File

@@ -1,28 +0,0 @@
package routes
import (
"net/http"
"github.com/mineatar-io/api-server/src/util"
"github.com/valyala/fasthttp"
)
func UUIDHandler(ctx *fasthttp.RequestCtx) {
user := ctx.UserValue("user").(string)
uuid, ok, err := util.LookupUUID(user)
if err != nil {
util.WriteError(ctx, err, http.StatusInternalServerError)
return
}
if !ok {
util.WriteError(ctx, nil, http.StatusNotFound)
return
}
ctx.SetBodyString(uuid)
}

View File

@@ -1,4 +1,4 @@
package util
package main
import (
"bytes"
@@ -9,34 +9,20 @@ import (
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
"github.com/mineatar-io/api-server/src/conf"
"github.com/gofiber/fiber/v2"
"github.com/mineatar-io/skin-render"
"github.com/mineatar-io/yggdrasil"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/valyala/fasthttp"
)
var (
Debug bool = os.Getenv("DEBUG") == "true"
yggdrasilUUIDLookupMetric = promauto.NewCounter(prometheus.CounterOpts{
Name: "yggdrasil_uuid_lookup_count",
Help: "The amount of Yggdrasil UUID lookup requests",
})
yggdrasilTextureLookupMetric = promauto.NewCounter(prometheus.CounterOpts{
Name: "yggdrasil_texture_lookup_count",
Help: "The amount of Yggdrasil texture lookup requests",
})
)
type QueryParams struct {
Scale int
Download bool
Overlay bool
Fallback bool
Scale int `query:"scale"`
Download bool `query:"download"`
Overlay bool `query:"overlay"`
Fallback bool `query:"fallback"`
}
func FormatUUID(uuid string) string {
@@ -59,10 +45,6 @@ func LookupUUID(value string) (string, bool, error) {
}
if ok {
if Debug {
log.Printf("Retrieved UUID from cache for '%s' (%s)\n", value, cache)
}
return cache, true, nil
}
@@ -72,13 +54,7 @@ func LookupUUID(value string) (string, bool, error) {
return "", false, err
}
yggdrasilUUIDLookupMetric.Inc()
if profile == nil {
if Debug {
log.Printf("Fetched UUID from Mojang for '%s', did not exist\n", value)
}
return "", false, nil
}
@@ -86,10 +62,6 @@ func LookupUUID(value string) (string, bool, error) {
return "", true, err
}
if Debug {
log.Printf("Fetched UUID from Mojang for '%s' (%s)\n", value, profile.UUID)
}
return profile.UUID, true, nil
}
@@ -100,10 +72,6 @@ func FetchImage(url string) (*image.NRGBA, error) {
return nil, err
}
if Debug {
log.Printf("Fetched image from URL: %s\n", url)
}
defer resp.Body.Close()
img, format, err := image.Decode(resp.Body)
@@ -143,10 +111,6 @@ func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
return nil, false, err
}
if Debug {
log.Printf("Retrieved skin for '%s' (slim: %t) from cache\n", uuid, slim)
}
return cache, slim, nil
}
@@ -156,22 +120,12 @@ func GetPlayerSkin(uuid string) (*image.NRGBA, bool, error) {
return nil, false, err
}
yggdrasilTextureLookupMetric.Inc()
if textures == nil {
if Debug {
log.Printf("Fetched textures for '%s' from Mojang, none exists, using default skin\n", uuid)
}
slim := skin.IsSlimFromUUID(uuid)
return skin.GetDefaultSkin(slim), slim, nil
}
if Debug {
log.Printf("Fetched textures for '%s' from Mojang\n", uuid)
}
value := ""
for _, property := range textures.Properties {
@@ -255,8 +209,8 @@ func EncodePNG(img image.Image) ([]byte, error) {
return buf.Bytes(), nil
}
func ParseQueryParams(ctx *fasthttp.RequestCtx, route conf.RouteConfig) *QueryParams {
args := ctx.QueryArgs()
func ParseQueryParams(ctx *fiber.Ctx, route RouteConfig) *QueryParams {
args := ctx.Context().QueryArgs()
response := &QueryParams{
Scale: route.DefaultScale,
@@ -286,16 +240,16 @@ func ParseQueryParams(ctx *fasthttp.RequestCtx, route conf.RouteConfig) *QueryPa
return response
}
func WriteError(ctx *fasthttp.RequestCtx, err error, statusCode int, body ...string) {
ctx.SetStatusCode(statusCode)
func GetInstanceID() (uint16, error) {
if instanceID := os.Getenv("INSTANCE_ID"); len(instanceID) > 0 {
value, err := strconv.ParseUint(instanceID, 10, 16)
if len(body) > 0 {
ctx.SetBodyString(body[0])
} else {
ctx.SetBodyString(http.StatusText(statusCode))
if err != nil {
log.Fatal(err)
}
return uint16(value), nil
}
if err != nil {
log.Println(err)
}
return 0, nil
}

View File

@@ -1,16 +0,0 @@
package util
import (
"github.com/mineatar-io/api-server/src/conf"
"github.com/mineatar-io/api-server/src/redis"
)
var (
config *conf.Configuration
r *redis.Redis
)
func Init(red *redis.Redis, c *conf.Configuration) {
r = red
config = c
}