package middleware import ( "context" "log/slog" "net" "net/http" "strings" "yobble-gateway-go/internal/logger" ) type contextKey string const realIPContextKey contextKey = "realIP" // RealIPMiddleware извлекает реальный IP-адрес клиента из заголовков и устанавливает его в контекст запроса. func RealIPMiddleware(next http.Handler) http.Handler { op := "middleware.RealIPMiddleware" log := logger.NewLoggerWithOp(op) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { realIP := r.Header.Get("X-Real-IP") if realIP == "" { forwardedFor := r.Header.Get("X-Forwarded-For") if forwardedFor != "" { parts := strings.Split(forwardedFor, ",") realIP = strings.TrimSpace(parts[0]) } } if realIP != "" { _, port, err := net.SplitHostPort(r.RemoteAddr) if err == nil { r.RemoteAddr = net.JoinHostPort(realIP, port) } else { r.RemoteAddr = realIP } ctx := context.WithValue(r.Context(), realIPContextKey, realIP) r = r.WithContext(ctx) log.Debug("real IP extracted", slog.String("ip", realIP)) } next.ServeHTTP(w, r) }) } // GetRealIP извлекает реальный IP из контекста запроса. func GetRealIP(r *http.Request) string { if ip, ok := r.Context().Value(realIPContextKey).(string); ok { return ip } return r.RemoteAddr } // RemoveTrailingSlashMiddleware перенаправляет запросы с завершающим слэшем (если это не просто '/') // на тот же путь без завершающего слэша. func RemoveTrailingSlashMiddleware(next http.Handler) http.Handler { op := "middleware.RemoveTrailingSlashMiddleware" log := logger.NewLoggerWithOp(op) return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { urlPath := r.URL.Path if urlPath != "" && urlPath != "/" && strings.HasSuffix(urlPath, "/") { newPath := strings.TrimSuffix(urlPath, "/") newURL := *r.URL newURL.Path = newPath log.Debug("redirecting trailing slash", slog.String("old_path", urlPath), slog.String("new_path", newPath)) http.Redirect(w, r, newURL.String(), http.StatusMovedPermanently) return } next.ServeHTTP(w, r) }) }