ApiGate
Типизированный API-шлюз (reverse proxy) для микросервисов на Rust.
Макросы генерируют маршруты с валидацией Path / Query / Json / Form / Multipart, хуками before, преобразованием map и runtime-политиками (балансировка, routing, таймауты). Внутри — axum, снаружи — только API apigate.
Быстрый старт
async
Сервис
Параметр service |
Описание |
|---|---|
name |
Имя сервиса = ключ .backend(...) в main |
prefix |
Внешний URL-префикс |
policy |
Политика по умолчанию для всех маршрутов сервиса |
Атрибуты маршрута
| Атрибут | Описание |
|---|---|
"/path" |
Внешний путь. Поддерживает /{param} |
to |
Путь в upstream. Без to — проксирует как есть (StripPrefix). Поддерживает /{param} |
path = T |
Десериализует и валидирует path-параметры (T: Deserialize + Clone). 400 при ошибке |
query = T |
Валидирует query string |
json = T |
Валидирует JSON body |
form = T |
Валидирует application/x-www-form-urlencoded body |
multipart |
Passthrough для multipart/form-data |
before = [...] |
Хуки, выполняемые до проксирования |
map = fn |
Преобразование query/json/form перед отправкой в upstream |
policy = "name" |
Переопределяет политику сервиса для этого маршрута |
json,form,multipart— взаимоисключающие (один body-режим на маршрут).
Входные данные
Path
async
Извлекается до хуков, попадает в RequestScope. Доступен в хуках как path: SaleIdPath (owned) или path: &SaleIdPath.
Query / Json / Form
// валидация query
// валидация JSON
// валидация form
Без map — валидация + passthrough оригинального тела. С map — преобразование перед отправкой.
Multipart
async
Passthrough без чтения тела. map не поддерживается.
Хуки (before)
Выполняются до проксирования. Работают с заголовками, URI, extensions.
async
async
Преобразование (map)
Типизированное преобразование query/json/form перед отправкой в upstream.
async
async
Работает аналогично для query = T, map = ... и form = T, map = ...:
- query: map переписывает query string в URI
- json: map сериализует результат в новое тело
- form: map сериализует результат в URL-encoded тело
Инъекция параметров в hook / map
Макрос анализирует типы параметров и генерирует код извлечения:
| Тип | Источник | Пример |
|---|---|---|
&mut PartsCtx<'_> |
Контекст запроса | ctx: &mut PartsCtx<'_> |
&mut RequestScope |
Прямой доступ к scope | scope: &mut RequestScope |
&T |
scope.get::<T>() — shared state / per-request данные |
config: &AuthConfig |
&mut T |
scope.get_mut::<T>() — только из local |
state: &mut Counter |
T (owned в hook) |
scope.take::<T>() — local, fallback clone из shared |
path: SaleIdPath |
T (первый owned в map) |
Входные данные (json/query/form) | input: PublicBuy |
Все параметры опциональны.
Ограничения: &mut PartsCtx / &mut RequestScope — макс. по одному; &mut T — макс. один и нельзя совмещать с &T; &mut RequestScope нельзя совмещать с &T / &mut T.
Таймауты
| Метод | Дефолт | Описание |
|---|---|---|
.request_timeout(Duration) |
30s | Полное время upstream-запроса. 504 при превышении |
.connect_timeout(Duration) |
5s | TCP handshake к backend'у |
.pool_idle_timeout(Duration) |
90s | Время жизни idle-соединений в connection pool |
Политики
Политика = routing (какие backend'ы) + balancing (какой конкретно). Дефолт: NoRouteKey + RoundRobin.
.policy
Приоритет: атрибут маршрута > политика сервиса > дефолтная.
Маршрутизация (routing)
Определяет набор кандидатов и опциональный affinity key для sticky sessions.
| Стратегия | Описание |
|---|---|
NoRouteKey |
Все backend'ы, без аффинности. Дефолт |
HeaderSticky::new("header") |
Affinity key из заголовка |
Кастомная стратегия
use ;
;
RouteCtx: service, route_path, method, uri, headers.
RoutingDecision: affinity: Option<AffinityKey>, candidates: CandidateSet (All | Indices(&[usize])).
Балансировка (balancing)
Выбирает конкретный backend из кандидатов.
| Стратегия | Описание |
|---|---|
RoundRobin::new() |
Циклический перебор. Дефолт |
ConsistentHash::new() |
Jump consistent hash по affinity key (xxh3). Без ключа — round-robin |
LeastRequest::new() |
Наименьшее число in-flight запросов |
LeastTime::new() |
Наименьшая EWMA-латентность |
Все балансировщики lock-free (атомарные операции).
Кастомный балансировщик
use ;
;
BalanceCtx: service, affinity, pool, candidates, candidate_len(), candidate_index(nth), candidate_backend(nth), is_candidate(idx).
ResultEvent: service, backend_index, status: Option<StatusCode>, error: Option<ProxyErrorKind>, head_latency: Duration.
Custom State
let app = builder
.state
.state
// ...
Доступ в хуках через &T:
async
State хранится в Arc<Extensions> — не клонируется на каждый запрос. scope.get::<T>() читает shared-ссылку. scope.insert() / scope.take() работают с per-request хранилищем.
Производительность
- Без
json/query/form— body проксируется без чтения (streaming) json = Tбезmap— валидация + passthrough оригинального тела- State:
Arc<Extensions>, 0 heap-аллокаций для read-only доступа - Pipeline: path + hooks + body в одном
Box::pin - HTTP-клиент:
TCP_NODELAY, connection pooling, keep-alive request_timeout→504 Gateway Timeout- Балансировщики lock-free (atomic counters)