// Package middleware provides HTTP middleware functions package middleware import ( "net/http" "time" "spiderman/internal/logger" "github.com/gorilla/mux" "github.com/sirupsen/logrus" ) // ResponseWriter wrapper to capture status code type responseWriter struct { http.ResponseWriter statusCode int size int } func (rw *responseWriter) WriteHeader(code int) { rw.statusCode = code rw.ResponseWriter.WriteHeader(code) } func (rw *responseWriter) Write(b []byte) (int, error) { size, err := rw.ResponseWriter.Write(b) rw.size += size return size, err } // Logging logs HTTP requests and responses func Logging(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() // Wrap the response writer wrapped := &responseWriter{ ResponseWriter: w, statusCode: http.StatusOK, } // Get route information if available route := mux.CurrentRoute(r) routeName := "" if route != nil { if name := route.GetName(); name != "" { routeName = name } else if pathTemplate, err := route.GetPathTemplate(); err == nil { routeName = pathTemplate } } // Create request logger requestLogger := logger.GetRequestLogger(r.Method, r.URL.Path, r.RemoteAddr).WithFields(logrus.Fields{ "user_agent": r.UserAgent(), "route": routeName, }) // Log incoming request requestLogger.Info("Request started") // Process request next.ServeHTTP(wrapped, r) // Calculate duration duration := time.Since(start) // Log completed request requestLogger.WithFields(logrus.Fields{ "status_code": wrapped.statusCode, "duration_ms": duration.Milliseconds(), "size_bytes": wrapped.size, }).Info("Request completed") // Log errors for 4xx and 5xx status codes if wrapped.statusCode >= 400 { requestLogger.WithFields(logrus.Fields{ "status_code": wrapped.statusCode, "duration_ms": duration.Milliseconds(), }).Warn("Request completed with error status") } }) } // CORS adds CORS headers func CORS(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { requestLogger := logger.GetRequestLogger(r.Method, r.URL.Path, r.RemoteAddr) // Enhanced CORS headers for Swagger UI support w.Header().Set("Access-Control-Allow-Origin", "*") w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, HEAD") w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization, Accept, X-Requested-With, Origin") w.Header().Set("Access-Control-Expose-Headers", "Content-Length, Content-Type") w.Header().Set("Access-Control-Allow-Credentials", "false") w.Header().Set("Access-Control-Max-Age", "86400") if r.Method == "OPTIONS" { requestLogger.Debug("Handling CORS preflight request") w.WriteHeader(http.StatusOK) return } next.ServeHTTP(w, r) }) } // Recovery recovers from panics and logs them func Recovery(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { defer func() { if err := recover(); err != nil { requestLogger := logger.GetRequestLogger(r.Method, r.URL.Path, r.RemoteAddr) requestLogger.WithFields(logrus.Fields{ "panic": err, }).Error("Panic recovered") http.Error(w, "Internal Server Error", http.StatusInternalServerError) } }() next.ServeHTTP(w, r) }) }