mirror of
https://github.com/Dvorinka/SpotifyRecAlg.git
synced 2026-06-03 20:13:03 +00:00
101 lines
2.5 KiB
Go
101 lines
2.5 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/hex"
|
|
"net/http"
|
|
"slices"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const requestIDKey = "request_id"
|
|
|
|
func requestID() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.GetHeader("X-Request-ID")
|
|
if id == "" {
|
|
id = newRequestID()
|
|
}
|
|
c.Set(requestIDKey, id)
|
|
c.Header("X-Request-ID", id)
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func accessLog(logger *zap.Logger) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
start := time.Now()
|
|
c.Next()
|
|
logger.Info("http request",
|
|
zap.String("method", c.Request.Method),
|
|
zap.String("path", c.FullPath()),
|
|
zap.Int("status", c.Writer.Status()),
|
|
zap.Duration("duration", time.Since(start)),
|
|
zap.String("request_id", requestIDFromContext(c)),
|
|
)
|
|
}
|
|
}
|
|
|
|
func recovery(logger *zap.Logger) gin.HandlerFunc {
|
|
return gin.CustomRecovery(func(c *gin.Context, recovered any) {
|
|
logger.Error("panic recovered", zap.Any("panic", recovered), zap.String("request_id", requestIDFromContext(c)))
|
|
problem(c, http.StatusInternalServerError, "https://spotify-rec.local/errors/internal", "Internal server error", "The server encountered an unexpected error.")
|
|
})
|
|
}
|
|
|
|
func apiKeyAuth(keys []string) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if len(keys) == 0 || c.Request.URL.Path == "/healthz" || c.Request.URL.Path == "/readyz" {
|
|
c.Next()
|
|
return
|
|
}
|
|
key := c.GetHeader("X-API-Key")
|
|
if !slices.Contains(keys, key) {
|
|
problem(c, http.StatusUnauthorized, "https://spotify-rec.local/errors/unauthorized", "Unauthorized", "A valid X-API-Key header is required.")
|
|
c.Abort()
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|
|
|
|
func newRequestID() string {
|
|
var b [16]byte
|
|
if _, err := rand.Read(b[:]); err != nil {
|
|
return time.Now().UTC().Format("20060102150405.000000000")
|
|
}
|
|
return hex.EncodeToString(b[:])
|
|
}
|
|
|
|
func requestIDFromContext(c *gin.Context) string {
|
|
value, ok := c.Get(requestIDKey)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
id, _ := value.(string)
|
|
return id
|
|
}
|
|
|
|
func cors() gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
origin := c.GetHeader("Origin")
|
|
if origin == "" {
|
|
origin = "*"
|
|
}
|
|
c.Header("Access-Control-Allow-Origin", origin)
|
|
c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
|
|
c.Header("Access-Control-Allow-Headers", "Content-Type, X-API-Key, X-Request-ID")
|
|
c.Header("Access-Control-Allow-Credentials", "true")
|
|
c.Header("Access-Control-Max-Age", "86400")
|
|
|
|
if c.Request.Method == "OPTIONS" {
|
|
c.AbortWithStatus(http.StatusNoContent)
|
|
return
|
|
}
|
|
c.Next()
|
|
}
|
|
}
|