package routes import ( "bytes" "dashboard/dao/sqldb" "dashboard/logger" "dashboard/models" "dashboard/pkg/jwt" "dashboard/pkg/rate" "dashboard/settings" "dashboard/utils" "io" "net" "net/http" "net/http/httputil" "os" "runtime/debug" "strconv" "strings" "time" "github.com/gin-gonic/gin" "go.uber.org/zap" ) func GinLogger(log *logger.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path query := c.Request.URL.RawQuery c.Next() cost := time.Since(start) log.Info(path, zap.Int("status", c.Writer.Status()), zap.String("method", c.Request.Method), zap.String("path", path), zap.String("query", query), zap.String("ip", c.ClientIP()), zap.String("user-agent", c.Request.UserAgent()), zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), zap.Duration("cost", cost), ) } } func GinRecovery(log *logger.Logger, stack bool) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { var brokenPipe bool if ne, ok := err.(*net.OpError); ok { if se, ok := ne.Err.(*os.SyscallError); ok { expectStr := strings.ToLower(se.Error()) if strings.Contains(expectStr, "broken pipe") || strings.Contains(expectStr, "connection reset by peer") { brokenPipe = true } } } httpRequest, _ := httputil.DumpRequest(c.Request, false) if brokenPipe { log.Error(c.Request.URL.Path, zap.Any("error", err), zap.String("request", string(httpRequest)), ) c.Error(err.(error)) c.Abort() return } if stack { log.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), zap.String("stack", string(debug.Stack())), ) } else { log.Error("[Recovery from panic]", zap.Any("error", err), zap.String("request", string(httpRequest)), ) } c.AbortWithStatus(http.StatusInternalServerError) } }() c.Next() } } func GinLog(log *logger.Logger) gin.HandlerFunc { return func(c *gin.Context) { c.Set(models.GinContxtLog, log) c.Next() } } func GinJwtAuthor(jwtC *jwt.Jwt) appHandler { return func(c *gin.Context) error { authHeader, err := c.Cookie(models.GinAuthorKey) if err != nil || !strings.HasPrefix(authHeader, models.GinAuthorPrefixKey) { c.Abort() return &models.BaseError{ Code: http.StatusUnauthorized, Msg: "Missing or invalid token", } } tokenStr := strings.TrimPrefix(authHeader, models.GinAuthorPrefixKey) _, err = jwtC.ParseToken(tokenStr) if err != nil { c.Abort() if jwt.ErrorIsJwtExpired(err) { return &models.BaseError{ Code: http.StatusUnauthorized, Msg: "Expired token", } } return &models.BaseError{ Code: http.StatusUnauthorized, Msg: "Invalid token", } } c.Next() return nil } } func GinRateLimit(rateC settings.RateLimitConfig) appHandler { lrate, err := rate.New(rate.WithCapacity(rateC.Capacity), rate.WithFillInterval(time.Duration(rateC.FillInterval)*time.Millisecond), ) if err != nil || lrate.Bucket == nil { panic(err) } return func(c *gin.Context) error { if !lrate.WaitMaxDuration(1, time.Duration(rateC.MaxWait)*time.Second) { time.Sleep(time.Duration(rateC.MaxWait) * time.Second) c.Abort() return &models.BaseError{ Code: http.StatusServiceUnavailable, Msg: "Exceeded rate limit", } } c.Next() return nil } } func GinCheckServerId(c *gin.Context) error { serverId := c.GetHeader("serverId") if serverId != serverId { c.Abort() return &models.BaseError{ Code: http.StatusServiceUnavailable, Msg: "Server Id error", } } c.Next() return nil } func GinStoreRequest(c *gin.Context) (err error) { defer func() { if err != nil { c.Abort() return } c.Next() }() log, _ := utils.GetLogFromContext(c) channel, _err := strconv.Atoi(c.GetHeader("channel")) if _err != nil { err = &models.BaseError{ Code: http.StatusServiceUnavailable, Msg: "Channel Id error", } return } oldConf, _err := sqldb.ResFulDataGet(c.Request.Method, c.Request.URL.String(), channel) if _err != nil { err = _err } bodyBytes, _err := io.ReadAll(c.Request.Body) if _err != nil { err = _err } c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) if oldConf != string(bodyBytes) { log.Sugar().Info("The resful request is not equel local store") if _err := sqldb.ResFulDataStore(c.Request.Method, c.Request.URL.String(), string(bodyBytes), channel); err != nil { err = _err } } return nil }