-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhttp.go
More file actions
127 lines (107 loc) · 3.1 KB
/
http.go
File metadata and controls
127 lines (107 loc) · 3.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
package main
import (
"fmt"
"github.com/chai2010/webp"
"github.com/gorilla/mux"
"io"
"net/http"
"os"
"path"
"strconv"
"strings"
"time"
)
// Init the HTTP (or rest api) server
func initHttp() {
r := mux.NewRouter()
r.HandleFunc("/{type}/{id}", handleAvatar).Methods("GET")
http.ListenAndServe(":8080", r)
}
// Handle the incoming avatar request.
func handleAvatar(w http.ResponseWriter, r *http.Request) {
// Ratelimiter
ip := getIP(r)
limiter := getRateLimiter(ip)
if !limiter.Allow() {
http.Error(w, http.StatusText(http.StatusTooManyRequests), http.StatusTooManyRequests)
return
}
// Default values passed from URL
vars := mux.Vars(r)
identifier := vars["id"]
mode := vars["type"]
scaleStr := r.URL.Query().Get("scale")
// Double check that the requested render is supported
if mode != "isometric" && mode != "body" && mode != "avatar" && mode != "head" {
http.Error(w, "Unsupported render mode. Valid render modes are isometric, head, body & avatar", http.StatusBadRequest)
return
}
// Initialize default scale
scale := 512
// Parse scale from string to int, if possible
if scaleStr != "" {
if parsedScale, err := strconv.Atoi(scaleStr); err == nil {
scale = parsedScale
} else {
fmt.Println("Invalid scale value, using default")
}
}
// To prevent overload on the server, we enforce some limits on scaling.
// Not smaller than 16, so that it's visible, and not more than 1024px
if scale < 16 {
scale = 16
} else if scale > 1024 {
scale = 1024
}
var uuid string
var err error
// Check if the supplied ID is a valid UUID
if isValidUUID(identifier) {
// We don't need the - in the UUID, so we remove it
uuid = strings.ReplaceAll(identifier, "-", "")
// Check if the supplied ID is a texture hash
} else if isValidSHA256Hash(identifier) {
uuid = identifier
} else {
// Supplied ID was likely a username. So we try to resolve it
uuid, err = getUUID(identifier)
if err != nil || uuid == "" {
uuid = strings.ReplaceAll(generateOfflineUUID(identifier).String(), "-", "")
}
}
cachePath := path.Join(renderDir, fmt.Sprintf("%s_%s_%s.web", mode, uuid, strconv.Itoa(scale)))
cachedFile, err := os.Open(cachePath)
if err == nil {
w.Header().Set("Content-Type", "image/webp")
_, err = io.Copy(w, cachedFile)
if err != nil {
http.Error(w, "Failed to read image: "+err.Error(), http.StatusInternalServerError)
}
return
}
defer cachedFile.Close()
// Request the skin from the MOJANG servers
skinPath, err := fetchSkin(uuid)
if err != nil {
skinPath = "fallback.png"
}
// Render the skin for the API
img, err := renderSkin(skinPath, mode, scale, uuid, true)
if err != nil {
http.Error(w, "Failed to render skin", http.StatusInternalServerError)
return
}
// Encode the image ready for browser rendering
f, _ := os.Create(cachePath)
err = webp.Encode(f, img, &webp.Options{
Lossless: true,
Quality: 100,
Exact: true,
})
if err != nil {
http.Error(w, "Failed to encode image: "+err.Error(), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "image/webp")
http.ServeContent(w, r, cachePath, time.Now(), f)
}