Что такое Tower и зачем она нужна?
Tower — это фундаментальная библиотека экосистемы Rust, предоставляющая абстракции для построения модульных, композируемых и асинхронных сетевых сервисов и промежуточного программного обеспечения (middleware). Она не является готовым HTTP-фреймворком (как Actix-web или Axum), а скорее "строительным материалом" для них. Axum, например, построен поверх Tower.
Основная идея Tower — это трейт Service, который представляет собой асинхронную функцию, преобразующую запрос (Request) в ответ (Response). Middleware (или "Layer") в Tower — это способ обернуть один сервис другим, добавляя к нему дополнительное поведение (логирование, аутентификацию, тайм-ауты, балансировку нагрузки и т.д.) без изменения исходного кода сервиса.
Зачем это нужно?
- Модульность: Вы можете разбить сложную логику обработки запросов на маленькие, независимые, переиспользуемые компоненты (сервисы и middleware).
- Композируемость: Middleware можно легко комбинировать, "накладывая" их друг на друга как слои (layers).
- Асинхронность: Tower спроектирована для работы с асинхронным Rust (tokio, async-std) и поддерживает futures.
- Тестируемость: Каждый компонент можно тестировать изолированно.
Установка Tower
Tower — это библиотека Rust, поэтому устанавливается она через менеджер пакетов Cargo. Добавьте зависимость в ваш файл Cargo.toml:
[dependencies]
tower = { version = "0.4", features = ["full"] } # "full" включает все фичи
tokio = { version = "1", features = ["full"] } # Для асинхронного рантайма
После этого выполните команду в терминале:
cargo build
Библиотека будет загружена и скомпилирована вместе с вашим проектом.
Быстрый старт: Ваш первый сервис и middleware
Давайте создадим простой сервис, который принимает строку и возвращает её же, но в верхнем регистре. Затем обернём его в middleware, которое логирует каждый запрос.
use std::convert::Infallible;
use std::task::{Context, Poll};
use tower::{Service, Layer, ServiceBuilder};
use tower::layer::util::Identity;
use std::future::{Ready, ready};
// 1. Определяем наш сервис
#[derive(Clone)]
struct MyService;
impl<Request> Service<Request> for MyService {
type Response = String;
type Error = Infallible;
type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: Request) -> Self::Future {
// Просто возвращаем строку
ready(Ok(format!("Hello from service! Request: {:?}", std::any::type_name::<Request>())))
}
}
// 2. Определяем middleware для логирования
#[derive(Clone)]
struct LogMiddleware;
impl<S> Layer<S> for LogMiddleware {
type Service = LogService<S>;
fn layer(&self, inner: S) -> Self::Service {
LogService { inner }
}
}
#[derive(Clone)]
struct LogService<S> {
inner: S,
}
impl<S, Request> Service<Request> for LogService<S>
where
S: Service<Request>,
S::Future: std::marker::Send,
Request: std::fmt::Debug,
{
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, req: Request) -> Self::Future {
println!("[LOG] Received request: {:?}", req);
self.inner.call(req)
}
}
#[tokio::main]
async fn main() {
// Собираем сервис с middleware с помощью ServiceBuilder
let mut service = ServiceBuilder::new()
.layer(LogMiddleware) // Добавляем наш middleware
.service(MyService); // Оборачиваем наш сервис
// Вызываем сервис
let response = service.call("Hello, World!".to_string()).await.unwrap();
println!("Response: {}", response);
}
В этом примере ServiceBuilder используется для компоновки middleware. Вывод будет:
[LOG] Received request: "Hello, World!"
Response: Hello from service! Request: alloc::string::String
Основные методы/функции и трейты Tower
1. trait Service
Что делает: Это центральный трейт библиотеки. Любой компонент, который может обрабатывать запросы, реализует этот трейт. Он определяет три ассоциированных типа (Response, Error, Future) и два метода.
Параметры:
Request— тип запроса (generic).Response— тип ответа.Error— тип ошибки.Future— тип будущего результата, который должен реализовыватьFuture<Output = Result<Self::Response, Self::Error>>.
Методы:
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>>— вызывается передcall, чтобы проверить, готов ли сервис принять запрос (например, не переполнен ли буфер).fn call(&mut self, req: Request) -> Self::Future— основной метод обработки запроса.
Пример: Смотрите "Быстрый старт" выше.
2. trait Layer
Что делает: Фабрика для создания middleware. Layer принимает один сервис (внутренний) и возвращает другой сервис (внешний, с добавленной логикой). Это основной способ композиции.
Параметры:
S— тип внутреннего сервиса.Service— ассоциированный тип, результирующий сервис.
Методы:
fn layer(&self, inner: S) -> Self::Service
Пример: Смотрите LogMiddleware в "Быстром старте".
3. ServiceBuilder
Что делает: Удобный строитель для компоновки нескольких Layer вокруг одного сервиса. Позволяет избежать глубокой вложенности вызовов layer.layer.layer.service().
Параметры: Нет параметров конструктора. Методы:
new() -> Self.layer<L>(self, layer: L) -> ServiceBuilder<...>— добавляет слой..buffer(self, capacity: usize) -> ServiceBuilder<...>— добавляет слой буферизации..concurrency_limit(self, max: usize) -> ServiceBuilder<...>— ограничивает количество одновременных запросов..rate_limit(self, num: u64, per: Duration) -> ServiceBuilder<...>— ограничивает частоту запросов..timeout(self, duration: Duration) -> ServiceBuilder<...>— устанавливает тайм-аут..service<S>(self, service: S) -> S::Service— финальная сборка, возвращает готовый сервис.
Пример:
use tower::ServiceBuilder;
use std::time::Duration;
let service = ServiceBuilder::new()
.buffer(1024)
.timeout(Duration::from_secs(5))
.rate_limit(10, Duration::from_secs(1))
.service(my_inner_service);
4. Buffer
Что делает: Middleware, которое добавляет буферизацию запросов. Позволяет сервису обрабатывать запросы асинхронно, не блокируя вызывающий код. Очень полезно, когда внутренний сервис не реализует Clone.
Параметры:
inner: S— внутренний сервис.capacity: usize— максимальное количество запросов в буфере.
Пример:
use tower::buffer::Buffer;
let buffered_service = Buffer::new(my_service, 100);
5. Timeout
Что делает: Middleware, которое ограничивает время выполнения запроса. Если сервис не отвечает за указанное время, возвращается ошибка Elapsed.
Параметры:
inner: S— внутренний сервис.timeout: Duration— максимальное время ожидания.
Пример:
use tower::timeout::Timeout;
use std::time::Duration;
let timed_service = Timeout::new(my_service, Duration::from_secs(3));
6. RateLimit
Что делает: Middleware для ограничения частоты запросов. Использует алгоритм "token bucket". Позволяет задать количество запросов за определенный период.
Параметры:
inner: S— внутренний сервис.num: u64— количество разрешенных запросов.per: Duration— период времени.
Пример:
use tower::rate_limit::RateLimit;
use std::time::Duration;
let rate_limited_service = RateLimit::new(my_service, 5, Duration::from_secs(1));
7. ConcurrencyLimit
Что делает: Middleware, которое ограничивает максимальное количество одновременно выполняемых запросов. Остальные запросы ждут в очереди.
Параметры:
inner: S— внутренний сервис.max: usize— максимальное количество одновременных запросов.
Пример:
use tower::concurrency_limit::ConcurrencyLimit;
let limited_service = ConcurrencyLimit::new(my_service, 10);
8. Retry
Что делает: Middleware для автоматических повторных попыток при ошибках. Позволяет задать политику повторения (сколько раз, какие ошибки обрабатывать).
Параметры:
inner: S— внутренний сервис.-
Также в библиотеке
Рекомендаций