package proxy import ( "log/slog" "math/rand" "sort" "strings" "yobble-gateway-go/internal/config" "yobble-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 }