106 lines
3.6 KiB
Go
106 lines
3.6 KiB
Go
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
|
||
}
|