Экземпляр и система функций
core.Instance — это центральный объект, который содержит все функции (DNS, маршрутизация, менеджеры прокси и т. д.) и управляет их жизненным циклом.
Структура экземпляра
// core/xray.go
type Instance struct {
statusLock sync.Mutex
features []features.Feature
pendingResolutions []resolution
pendingOptionalResolutions []resolution
running bool
resolveLock sync.Mutex
ctx context.Context
}По сути, экземпляр представляет собой контейнер внедрения зависимостей. Функции регистрируют себя, а другие функции могут объявлять зависимости, которые разрешаются, когда все необходимые функции становятся доступны.
Интерфейс Feature
Каждый основной компонент реализует интерфейс features.Feature:
// features/feature.go
type Feature interface {
common.HasType // Type() interface{}
common.Runnable // Start() error, Close() error
}Типы функций идентифицируются сигнальными значениями указателей:
func ManagerType() interface{} { return (*Manager)(nil) }
func ClientType() interface{} { return (*Client)(nil) }Последовательность инициализации
sequenceDiagram
participant Config
participant Instance
participant Feature
participant InboundMgr
participant OutboundMgr
Config->>Instance: New(config)
loop Для каждой конфигурации приложения
Instance->>Instance: CreateObject(settings)
Instance->>Feature: AddFeature(feature)
Feature-->>Instance: Разрешение ожидающих зависимостей
end
Note over Instance: Авторегистрация значений по умолчанию<br/>(DNS, Policy, Router, Stats)
Instance->>Instance: InitSystemDialer(dns, obm)
Instance->>Instance: Проверка: все зависимости разрешены?
loop Для каждой входящей конфигурации
Instance->>InboundMgr: AddHandler(handler)
end
loop Для каждой исходящей конфигурации
Instance->>OutboundMgr: AddHandler(handler)
end
Note over Instance: Экземпляр готов (не запущен)
Instance->>Instance: Start()
loop Для каждой функции
Instance->>Feature: Start()
endКлючевые шаги в initInstanceWithConfig()
Функции приложения: Перебор
config.App(диспетчер, маршрутизатор, DNS, политики, статистика и т. д.), создание объектов черезCreateObject(), регистрация в качестве функций.Обязательные значения по умолчанию: Если DNS, Policy, Router или Stats не были сконфигурированы, регистрируются реализации по умолчанию (no-op):
goessentialFeatures := []struct{ Type, Instance }{ {dns.ClientType(), localdns.New()}, {policy.ManagerType(), policy.DefaultManager{}}, {routing.RouterType(), routing.DefaultRouter{}}, {stats.ManagerType(), stats.NoopManager{}}, }Инициализация системного дайлера: Настройка глобального системного дайлера с DNS-клиентом и менеджером исходящих обработчиков (для DNS-разрешения в исходящем обработчике
freedom).Проверка зависимостей: Если какие-либо
pendingResolutionsостались неразрешёнными, возвращается ошибка.Обработчики: Добавление всех входящих и исходящих обработчиков.
Разрешение зависимостей
Механизм RequireFeatures() обеспечивает ленивое внедрение зависимостей:
// Компонент объявляет, что ему необходимо:
core.RequireFeatures(ctx, func(om outbound.Manager, router routing.Router,
pm policy.Manager, sm stats.Manager, dc dns.Client) error {
return d.Init(config, om, router, pm, sm)
})Когда вызывается RequireFeatures():
- Проверяется, зарегистрированы ли уже все типы параметров
- Если да, обратный вызов выполняется немедленно
- Если нет, сохраняется как
resolutionвpendingResolutions - При каждом вызове
AddFeature()повторно проверяются все ожидающие разрешения
Разрешение использует рефлексию для сопоставления типов параметров:
type resolution struct {
deps []reflect.Type // требуемые типы функций
callback interface{} // func(features...) error
}Создание объектов
CreateObject() — это фабричная функция, которая преобразует protobuf-конфигурации в объекты времени выполнения:
// Вызывается из core/config.go
func CreateObject(server *Instance, config interface{}) (interface{}, error) {
ctx := context.WithValue(server.ctx, xrayKey, server)
return common.CreateObject(ctx, config)
}Это обращается к реестру конфигураций, где каждый protobuf-тип сопоставлен с функцией-конструктором, зарегистрированной через common.RegisterConfig().
Паттерн реестра конфигураций
Каждый компонент следует этому паттерну:
func init() {
common.Must(common.RegisterConfig((*Config)(nil),
func(ctx context.Context, config interface{}) (interface{}, error) {
c := config.(*Config)
// Создание объекта времени выполнения из конфигурации
return NewHandler(ctx, c)
}))
}Protobuf-тип Config является ключом, а фабричная функция создаёт объект времени выполнения. Обёртка TypedMessage (protobuf Any) обеспечивает типобезопасную десериализацию.
Заметки по реализации
Для повторной реализации системы Instance:
- Необходим реестр функций, сопоставляющий типы функций с экземплярами
- Резолвер зависимостей, который вызывает обратные вызовы, когда все зависимости удовлетворены
- Десериализатор конфигураций, сопоставляющий типы конфигураций с конструкторами
- Жизненный цикл функции: регистрация -> разрешение зависимостей -> запуск -> закрытие
Подход на основе рефлексии может быть заменён явной регистрацией в статически типизированных языках:
instance.addFeature(dispatcherFeature)
instance.addFeature(routerFeature)
// Router видит, что dispatcher доступен, и разрешает свою зависимость