AndreyIgorevich d9ffefd5f6 Add project
2025-07-10 01:10:47 +07:00

106 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package proxy
import (
"log/slog"
"math/rand"
"sort"
"strings"
"yobbly-gateway-go/internal/config"
"yobbly-gateway-go/internal/logger"
)
// findBestMatch ищет самый длинный совпадающий префикс в заданных маршрутах.
// Он возвращает найденный префикс и остаток пути (хвост).
func findBestMatch(versionRoutes map[string][]config.Backend, path string) (string, string) {
op := "proxy.findBestMatch"
log := logger.NewLoggerWithOp(op)
var prefixes []string
for prefix := range versionRoutes {
prefixes = append(prefixes, prefix)
}
// Сортируем префиксы по длине в порядке убывания, чтобы сначала найти самый длинный
sort.Slice(prefixes, func(i, j int) bool {
return len(prefixes[i]) > len(prefixes[j])
})
for _, prefix := range prefixes {
if strings.HasPrefix(path, prefix) {
log.Debug("found best match", slog.String("path", path), slog.String("prefix", prefix))
return prefix, path[len(prefix):]
}
}
log.Debug("no best match found", slog.String("path", path))
return "", ""
}
// SelectBackend выбирает бэкенд из списка доступных серверов.
// В настоящее время он выбирает случайный, что соответствует реализации на Python.
func selectBackend(backends []config.Backend) *config.Backend {
op := "proxy.selectBackend"
log := logger.NewLoggerWithOp(op)
if len(backends) == 0 {
log.Warn("no backends available")
return nil
}
selected := &backends[rand.Intn(len(backends))]
log.Debug("selected backend", slog.String("url", selected.URL))
return selected
}
// ResolveBackend находит подходящий сервис бэкенда для заданного пути запроса.
// Он анализирует версию, находит наиболее подходящий маршрут и выбирает сервер бэкенда.
func ResolveBackend(path string, routes config.Routes) (*config.Backend, string) {
op := "proxy.ResolveBackend"
log := logger.NewLoggerWithOp(op)
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) == 0 {
log.Debug("empty path after trimming", slog.String("path", path))
return nil, ""
}
var versionRoutes map[string][]config.Backend
var pathToMatch string
// Проверяем, является ли первая часть пути строкой версии
if routes[parts[0]] != nil {
versionRoutes = routes[parts[0]]
if len(parts) > 1 {
pathToMatch = "/" + strings.Join(parts[1:], "/")
} else {
pathToMatch = "/"
}
log.Debug("versioned route detected", slog.String("version", parts[0]), slog.String("pathToMatch", pathToMatch))
} else {
// Возвращаемся к версии по умолчанию
versionRoutes = routes["default"]
pathToMatch = path
log.Debug("default route used", slog.String("pathToMatch", pathToMatch))
}
if versionRoutes == nil {
log.Warn("no version routes found", slog.String("path", path))
return nil, ""
}
routePrefix, tailPath := findBestMatch(versionRoutes, pathToMatch)
if routePrefix == "" {
log.Warn("no route prefix found", slog.String("path", pathToMatch))
return nil, ""
}
backend := selectBackend(versionRoutes[routePrefix])
if backend == nil {
log.Warn("no backend selected for route prefix", slog.String("route_prefix", routePrefix))
return nil, ""
}
log.Debug("resolved backend", slog.String("backend_url", backend.URL), slog.String("tail_path", tailPath))
return backend, tailPath
}