Files
goplt/internal/server/middleware.go
0x1d 52d48590ae fix: resolve all linting and formatting issues
- Fix error return value checks (errcheck)
- Fix unused parameters by using underscore prefix
- Add missing package comments to all packages
- Fix context key type issue in middleware (use typed contextKey)
- Replace deprecated trace.NewNoopTracerProvider with noop.NewTracerProvider
- Fix embedded field selector in database client
- Remove trailing whitespace
- Remove revive linter (as requested) to avoid stuttering warnings for public API interfaces

All linting and formatting checks now pass.
2025-11-05 20:48:59 +01:00

144 lines
3.5 KiB
Go

// 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()
}
}