85 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			85 lines
		
	
	
		
			2.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
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
 | 
						||
 | 
						||
        // Skip redirect for Socket.IO and WebSocket upgrade traffic
 | 
						||
        // Socket.IO may rely on a trailing slash in polling endpoints.
 | 
						||
        if strings.HasPrefix(urlPath, "/socket.io") || strings.HasPrefix(urlPath, "/ws/socket.io") ||
 | 
						||
            strings.EqualFold(r.Header.Get("Upgrade"), "websocket") ||
 | 
						||
            strings.Contains(strings.ToLower(r.Header.Get("Connection")), "upgrade") {
 | 
						||
            next.ServeHTTP(w, r)
 | 
						||
            return
 | 
						||
        }
 | 
						||
 | 
						||
        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)
 | 
						||
	})
 | 
						||
}
 |