Tower для Rust: полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Что такое 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 для автоматических повторных попыток при ошибках. Позволяет задать политику повторения (сколько раз, какие ошибки обрабатывать).

Параметры:

Рекомендаций