Волга
Главная
API Docs
GitHub
  • English
  • Русский
Главная
API Docs
GitHub
  • English
  • Русский
  • Главная
  • Старт

    • Быстрый старт
    • Параметры маршрута
    • Параметры запроса
    • Группировка маршрутов
  • Запросы и ответы

    • Заголовки (Headers)
    • Работа с JSON
    • Работа с Form Data
    • Работа с файлами
    • Cookies
  • Middleware и инфраструктура

    • Основы Middleware
    • Пользовательские Middleware
    • Параметризованные Middleware
    • Сжатие ответов
    • Распаковка запросов
    • CORS (Cross-Origin Resource Sharing)
    • Статические файлы
    • Rate Limiting
    • Файлы конфигурации
  • Безопасность и доступ

    • Аутентификация и авторизация
  • Надежность и наблюдаемость

    • Центральный обработчик ошибок
    • Логгирование и Трассировка
    • Отмена запросов
  • Протоколы и realtime

    • HTTP/1 и HTTP/2
    • HTTPS
    • WebSockets
    • Server-Sent Events (SSE)
  • Продвинутые паттерны

    • Внедрение Зависимостей
    • Пользовательская обработка методов HEAD, OPTIONS и TRACE

Параметризованные Middleware

Помимо встроенных middleware-замыканий, регистрируемых через wrap() и with(), Volga также поддерживает регистрацию middleware в виде повторно используемых настраиваемых типов через метод attach().

Такой подход удобен, когда middleware требует собственного состояния, конфигурации или должен использоваться повторно в нескольких приложениях.

Обзор

Параметризованный middleware — это обычный Rust-тип (как правило, struct), реализующий трейт Middleware. Такой тип хранит всю необходимую конфигурацию и разделяемое состояние, а его метод call() содержит основную логику middleware.

Это похоже на паттерны middleware в других экосистемах (например, слои Tower, middleware ASP.NET Core или классы Express.js).

Трейт Middleware

Трейт определён следующим образом:

pub trait Middleware: Send + Sync + 'static {
    fn call(
        &self,
        ctx: HttpContext,
        next: NextFn,
    ) -> impl Future<Output = HttpResult> + Send + 'static;
}

Любой тип, реализующий этот трейт, можно передать в attach().

Пример: Middleware Timeout

Ниже приведён небольшой middleware, добавляющий искусственную задержку перед дальнейшей обработкой запроса. Длительность задержки настраивается при регистрации:

use std::time::Duration;
use volga::{App, HttpResult, middleware::{HttpContext, NextFn, Middleware}};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    // Регистрируем параметризованный middleware
    app.attach(Timeout {
        duration: Duration::from_secs(1),
    });

    app.map_get("/hello", || async { "Hello, World!" });

    app.run().await
}

struct Timeout {
    duration: Duration,
}

impl Middleware for Timeout {
    fn call(&self, ctx: HttpContext, next: NextFn) -> impl Future<Output = HttpResult> + 'static {
        let duration = self.duration;
        async move {
            tokio::time::sleep(duration).await;
            next(ctx).await
        }
    }
}

Структура Timeout хранит свою конфигурацию (duration) и реализует логику middleware внутри call(). Можно создать несколько экземпляров с разными значениями задержки или переиспользовать один экземпляр для нескольких маршрутов.

.wrap() и .attach()

Оба метода регистрируют middleware, работающие с полным HttpContext, но рассчитаны на разные сценарии:

  • wrap() оптимизирован для коротких встроенных замыканий. Аннотации типов для ctx и next не требуются.
  • attach() предназначен для повторно используемых параметризованных типов middleware — как правило, структур, реализующих трейт Middleware.

Совет

Используйте wrap() для быстрых встроенных middleware и attach(), когда нужно оформить middleware как именованный настраиваемый тип, пригодный для повторного использования.

attach() также принимает замыкания, но в этом случае требуется указывать аннотации типов для аргументов:

use volga::{App, middleware::{HttpContext, NextFn}};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    app.attach(|ctx: HttpContext, next: NextFn| async move {
        next(ctx).await
    });

    app.run().await
}

Регистрация на маршрутах и группах маршрутов

Параметризованные middleware могут быть прикреплены не только ко всему приложению, но и к отдельным маршрутам и группам маршрутов:

use std::time::Duration;
use volga::{App, HttpResult, middleware::{HttpContext, NextFn, Middleware}};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    // Прикрепление к одному маршруту
    app
        .map_get("/hello", || async { "Hello, World!" })
        .attach(Timeout { duration: Duration::from_secs(1) });

    // Прикрепление к группе маршрутов
    app.group("/api", |api| {
        api.attach(Timeout { duration: Duration::from_secs(2) });
        api.map_get("/ping", || async { "pong" });
    });

    app.run().await
}

struct Timeout {
    duration: Duration,
}

impl Middleware for Timeout {
    fn call(&self, ctx: HttpContext, next: NextFn) -> impl Future<Output = HttpResult> + 'static {
        let duration = self.duration;
        async move {
            tokio::time::sleep(duration).await;
            next(ctx).await
        }
    }
}

Когда использовать параметризованные Middleware

Выбирайте attach() и отдельный тип, если:

  • Middleware требует конфигурации при регистрации (таймауты, лимиты, feature flags и т. п.).
  • Middleware должен быть переиспользуемым между проектами или крейтами.
  • Middleware хранит разделяемое состояние, счётчики или дескрипторы внешних систем.
  • Вы хотите юнит-тестировать middleware независимо от работающего приложения.

Для простых разовых преобразований, как правило, лаконичнее оставить логику встроенной через wrap() или with().

Встроенные возможности, такие как CORS, аутентификация и ограничение частоты запросов, сами реализованы как параметризованные middleware поверх attach().

Другие варианты Middleware

Тот же параметризованный подход работает и для остальных middleware-трейтов. Помимо Middleware, вы можете реализовать Filter, TapReq, MapOk, MapErr и With на собственных типах. Они регистрируются теми же методами, что и их аналоги-замыкания — filter(), tap_req(), map_ok(), map_err() и with() соответственно. Например, переиспользуемый параметризованный фильтр:

use volga::{App, headers::HttpHeaders, middleware::Filter};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    app.map_get("/sum/{x}/{y}", |x: i32, y: i32| async move { x + y })
        .filter(HasHeader {
            header: "x-api-key".to_owned()
        });

    app.run().await
}

#[derive(Clone)]
struct HasHeader {
    header: String
}

impl Filter<HttpHeaders> for HasHeader {
    type Output = bool;

    async fn filter(&self, headers: HttpHeaders) -> bool {
        headers.get_raw(&self.header).is_some()
    }
}
Последнее обновление: 06.04.2026, 13:04
Prev
Пользовательские Middleware
Next
Сжатие ответов