// Package server provides HTTP middleware functions for request processing. package server import ( "context" "net/http" "runtime" "time" "git.dcentral.systems/toolz/goplt/pkg/errorbus" "git.dcentral.systems/toolz/goplt/pkg/logger" "github.com/gin-gonic/gin" "github.com/google/uuid" ) type contextKey string const ( requestIDKey contextKey = "request_id" userIDKey contextKey = "user_id" ) // RequestIDMiddleware generates a unique request ID for each request. func RequestIDMiddleware() gin.HandlerFunc { return func(c *gin.Context) { requestID := c.GetHeader("X-Request-ID") if requestID == "" { requestID = uuid.New().String() } c.Set(string(requestIDKey), requestID) c.Header("X-Request-ID", requestID) c.Next() } } // LoggingMiddleware logs all HTTP requests with structured logging. func LoggingMiddleware(log logger.Logger) gin.HandlerFunc { return func(c *gin.Context) { start := time.Now() path := c.Request.URL.Path method := c.Request.Method // Process request c.Next() // Calculate duration duration := time.Since(start) // Get request ID from context requestID, _ := c.Get(string(requestIDKey)) requestIDStr := "" if id, ok := requestID.(string); ok { requestIDStr = id } // Log request log.Info("HTTP request", logger.String("method", method), logger.String("path", path), logger.Int("status", c.Writer.Status()), logger.Any("duration_ms", duration.Milliseconds()), logger.String("request_id", requestIDStr), logger.String("ip", c.ClientIP()), ) } } // PanicRecoveryMiddleware recovers from panics and publishes them to the error bus. func PanicRecoveryMiddleware(errorBus errorbus.ErrorPublisher) gin.HandlerFunc { return func(c *gin.Context) { defer func() { if err := recover(); err != nil { // Capture stack trace stack := make([]byte, 4096) n := runtime.Stack(stack, false) stack = stack[:n] // Get request ID from context requestID, _ := c.Get(string(requestIDKey)) ctx := context.WithValue(context.Background(), requestIDKey, requestID) // Create error var panicErr error if e, ok := err.(error); ok { panicErr = e } else { panicErr = &panicError{value: err, stack: stack} } // Publish to error bus errorBus.Publish(ctx, panicErr) // Return error response c.JSON(http.StatusInternalServerError, gin.H{ "error": "Internal server error", }) c.Abort() } }() c.Next() } } // panicError wraps a panic value as an error. type panicError struct { value interface{} stack []byte } func (e *panicError) Error() string { return "panic recovered" } // CORSMiddleware provides CORS support. func CORSMiddleware() gin.HandlerFunc { return func(c *gin.Context) { c.Writer.Header().Set("Access-Control-Allow-Origin", "*") c.Writer.Header().Set("Access-Control-Allow-Credentials", "true") c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With") c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT, DELETE, PATCH") if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusNoContent) return } c.Next() } } // TimeoutMiddleware sets a request timeout. func TimeoutMiddleware(timeout time.Duration) gin.HandlerFunc { return func(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), timeout) defer cancel() c.Request = c.Request.WithContext(ctx) c.Next() } }