Middleware в Axum
Middleware в Axum — это компоненты, которые обрабатывают HTTP-запросы до или после основного обработчика. Они позволяют добавлять функциональность, такую как аутентификация, логирование, сжатие и другую бизнес-логику, не дублируя код в каждом обработчике.
Концепция middleware в Axum
В Axum middleware реализуются с помощью нескольких механизмов:
- Слои (Layers) — часть экосистемы tower, которая позволяет создавать компоненты промежуточного ПО
- Расширения (Extensions) — хранилище типизированных данных, доступных во время обработки запроса
- Экстракторы (Extractors) — компоненты для извлечения данных из запроса
- Вложенные роутеры (Nested routers) — для применения middleware к определенным группам маршрутов
Основные типы middleware
1. Tower middleware
Tower — это библиотека для построения надежных сетевых сервисов, на которой основана Axum. Tower предоставляет несколько готовых middleware, которые можно использовать в Axum:
use axum::{
Router,
routing::get,
};
use tower_http::{
trace::TraceLayer,
compression::CompressionLayer,
timeout::TimeoutLayer,
};
use std::time::Duration;
async fn handler() -> &'static str {
"Привет, мир!"
}
let app = Router::new()
.route("/", get(handler))
// Добавление слоя трассировки для логирования запросов
.layer(TraceLayer::new_for_http())
// Добавление автоматического сжатия ответов
.layer(CompressionLayer::new())
// Добавление тайм-аута в 30 секунд для всех запросов
.layer(TimeoutLayer::new(Duration::from_secs(30)));
2. Создание собственного middleware с помощью tower::Service
Вы можете создать собственный middleware, реализовав трейт tower::Service
:
use axum::{
body::Body,
http::{Request, StatusCode},
response::{IntoResponse, Response},
Router,
routing::get,
};
use futures::future::BoxFuture;
use std::{
task::{Context, Poll},
pin::Pin,
};
use tower::{Layer, Service};
// Структура middleware
#[derive(Clone)]
struct AuthMiddleware<S> {
inner: S,
}
// Реализация Service для AuthMiddleware
impl<S> Service<Request<Body>> for AuthMiddleware<S>
where
S: Service<Request<Body>, Response = Response> + Clone + Send + 'static,
S::Future: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
type Future = BoxFuture<'static, Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.inner.poll_ready(cx)
}
fn call(&mut self, request: Request<Body>) -> Self::Future {
// Клонируем сервис, так как мы будем использовать его асинхронно
let mut inner = self.inner.clone();
Box::pin(async move {
// Проверяем наличие токена авторизации
if let Some(auth_header) = request.headers().get("Authorization") {
if let Ok(auth_str) = auth_header.to_str() {
if auth_str.starts_with("Bearer ") {
// Токен существует, продолжаем обработку запроса
return inner.call(request).await;
}
}
}
// Если токен отсутствует, возвращаем ошибку авторизации
Ok(Response::builder()
.status(StatusCode::UNAUTHORIZED)
.body(Body::from("Требуется авторизация"))
.unwrap())
})
}
}
// Layer для AuthMiddleware
#[derive(Clone)]
struct AuthLayer;
impl<S> Layer<S> for AuthLayer {
type Service = AuthMiddleware<S>;
fn layer(&self, service: S) -> Self::Service {
AuthMiddleware { inner: service }
}
}
async fn protected_handler() -> &'static str {
"Защищенные данные"
}
let app = Router::new()
.route("/protected", get(protected_handler))
// Применяем наш middleware аутентификации
.layer(AuthLayer);
3. Создание простых middleware с помощью функций-обработчиков
Для более простых случаев можно использовать AsyncMiddleware:
use std::time::Instant;
use axum::{
http::{Request, Response},
middleware::{self, Next},
routing::get,
Router,
body::Body,
};
// Middleware для измерения времени выполнения запроса
async fn timing_middleware(
req: Request<Body>,
next: Next<Body>,
) -> Response {
let start = Instant::now();
// Передаем запрос следующему обработчику
let response = next.run(req).await;
// Измеряем время после выполнения
let duration = start.elapsed();
println!("Запрос обработан за: {:?}", duration);
response
}
async fn handler() -> &'static str {
"Привет, мир!"
}
// Создаем маршрутизатор с middleware
let app = Router::new()
.route("/", get(handler))
// Применяем middleware ко всем маршрутам
.layer(middleware::from_fn(timing_middleware));
Применение middleware к группам маршрутов
Вы можете применять middleware только к определенным группам маршрутов:
use axum::{
Router,
routing::{get, post},
middleware,
};
async fn public_handler() -> &'static str {
"Публичные данные"
}
async fn admin_handler() -> &'static str {
"Панель администратора"
}
async fn user_handler() -> &'static str {
"Профиль пользователя"
}
async fn auth_middleware(
req: Request<Body>,
next: Next<Body>,
) -> Response {
// Логика проверки авторизации
// ...
next.run(req).await
}
async fn admin_middleware(
req: Request<Body>,
next: Next<Body>,
) -> Response {
// Логика проверки прав администратора
// ...
next.run(req).await
}
// Создаем отдельные роутеры
let public_routes = Router::new()
.route("/", get(public_handler));
let user_routes = Router::new()
.route("/profile", get(user_handler))
// Применяем middleware аутентификации только к маршрутам пользователей
.layer(middleware::from_fn(auth_middleware));
let admin_routes = Router::new()
.route("/dashboard", get(admin_handler))
// Применяем middleware администратора
.layer(middleware::from_fn(admin_middleware))
// И базовую аутентификацию
.layer(middleware::from_fn(auth_middleware));
// Объединяем все маршруты
let app = Router::new()
.nest("/", public_routes)
.nest("/user", user_routes)
.nest("/admin", admin_routes);
Расширения (Extensions)
Extensions позволяют передавать данные между middleware и обработчиками:
use axum::{
extract::Extension,
middleware::{self, Next},
response::IntoResponse,
routing::get,
Router,
body::Body,
http::Request,
};
use std::sync::{Arc, Mutex};
// Структура для хранения статистики
#[derive(Default, Clone)]
struct AppStats {
request_count: Arc<Mutex<usize>>,
}
// Middleware для подсчета запросов
async fn stats_middleware(
Extension(stats): Extension<AppStats>,
req: Request<Body>,
next: Next<Body>,
) -> impl IntoResponse {
// Увеличиваем счетчик запросов
let mut count = stats.request_count.lock().unwrap();
*count += 1;
let current_count = *count;
drop(count);
// Выводим текущее количество запросов
println!("Запрос #{}", current_count);
// Продолжаем обработку
next.run(req).await
}
// Обработчик, использующий статистику
async fn stats_handler(Extension(stats): Extension<AppStats>) -> String {
let count = stats.request_count.lock().unwrap();
format!("Всего запросов: {}", *count)
}
// Инициализация статистики
let stats = AppStats::default();
// Создаем маршрутизатор
let app = Router::new()
.route("/", get(|| async { "Привет, мир!" }))
.route("/stats", get(stats_handler))
// Добавляем middleware
.layer(middleware::from_fn_with_state(
stats.clone(),
|state, req, next| stats_middleware(Extension(state), req, next)
))
// Добавляем статистику в глобальные расширения
.layer(Extension(stats));
Порядок выполнения middleware
Порядок добавления middleware важен: они выполняются снаружи внутрь (как луковица):
let app = Router::new()
.route("/", get(handler))
.layer(middleware_1) // Выполняется первым
.layer(middleware_2) // Выполняется вторым
.layer(middleware_3); // Выполняется третьим
// Порядок выполнения: middleware_3 -> middleware_2 -> middleware_1 -> handler
Обработка ошибок в middleware
В middleware можно перехватывать и обрабатывать ошибки:
use axum::{
middleware::{self, Next},
response::IntoResponse,
routing::get,
Router,
body::Body,
http::{Request, StatusCode},
};
use std::convert::Infallible;
// Обработчик, который может завершиться с ошибкой
async fn fallible_handler() -> Result<&'static str, StatusCode> {
// Имитация ошибки
Err(StatusCode::INTERNAL_SERVER_ERROR)
}
// Middleware для обработки ошибок
async fn error_handling_middleware(
req: Request<Body>,
next: Next<Body>,
) -> Result<impl IntoResponse, Infallible> {
// Запускаем следующий обработчик
let response = next.run(req).await;
// Проверяем статус-код ответа
if response.status().is_server_error() {
println!("Произошла серверная ошибка: {}", response.status());
// Логика для уведомления об ошибке (например, отправка в систему мониторинга)
// ...
// Можно изменить ответ, чтобы показать пользовательское сообщение об ошибке
return Ok((
StatusCode::INTERNAL_SERVER_ERROR,
"Извините, произошла внутренняя ошибка сервера. Наша команда уже работает над этим."
).into_response());
}
// Если ошибки нет, просто передаем исходный ответ
Ok(response)
}
let app = Router::new()
.route("/api", get(fallible_handler))
.layer(middleware::from_fn(error_handling_middleware));
Популярные middleware в экосистеме Axum
CORS (Cross-Origin Resource Sharing)
use axum::{
Router,
routing::get,
};
use tower_http::cors::{CorsLayer, Any};
async fn handler() -> &'static str {
"API-данные"
}
let cors = CorsLayer::new()
// Разрешаем запросы с любого источника
.allow_origin(Any)
// Разрешаем только GET и POST методы
.allow_methods([http::Method::GET, http::Method::POST])
// Разрешаем заголовки Content-Type
.allow_headers([http::header::CONTENT_TYPE]);
let app = Router::new()
.route("/api", get(handler))
.layer(cors);
Кэширование
use axum::{
Router,
routing::get,
middleware::{self, Next},
response::Response,
body::Body,
http::{Request, HeaderValue, header},
};
async fn cache_middleware(
req: Request<Body>,
next: Next<Body>,
) -> Response {
let mut response = next.run(req).await;
// Добавляем заголовки кэширования для статических ресурсов
if req.uri().path().starts_with("/static/") {
let headers = response.headers_mut();
headers.insert(
header::CACHE_CONTROL,
HeaderValue::from_static("public, max-age=86400"), // 24 часа
);
}
response
}
let app = Router::new()
// Маршруты для статических файлов
.route("/static/*path", get(serve_static))
.layer(middleware::from_fn(cache_middleware));
Сжатие
use axum::{
Router,
routing::get,
};
use tower_http::compression::{CompressionLayer, CompressionLevel};
let app = Router::new()
.route("/api/large-data", get(large_data_handler))
.layer(
CompressionLayer::new()
// Настройка поддерживаемых алгоритмов
.gzip(true)
.deflate(true)
.br(true)
// Задание уровня сжатия
.level(CompressionLevel::Default)
);
Ограничение скорости запросов (Rate Limiting)
use axum::{
extract::Extension,
middleware::{self, Next},
response::{IntoResponse, Response},
routing::get,
Router,
body::Body,
http::{Request, StatusCode},
};
use std::{
collections::HashMap,
net::IpAddr,
sync::{Arc, Mutex},
time::{Duration, Instant},
};
// Структура для хранения данных о частоте запросов
struct RateLimiter {
// IP -> (последний запрос, количество запросов)
clients: Mutex<HashMap<IpAddr, (Instant, usize)>>,
// Максимальное количество запросов в указанный интервал
max_requests: usize,
// Интервал сброса счетчика
window: Duration,
}
impl RateLimiter {
fn new(max_requests: usize, window: Duration) -> Self {
Self {
clients: Mutex::new(HashMap::new()),
max_requests,
window,
}
}
// Проверяет, не превышен ли лимит запросов
fn check(&self, ip: IpAddr) -> bool {
let mut clients = self.clients.lock().unwrap();
let now = Instant::now();
// Получаем или создаем запись для IP
let entry = clients.entry(ip).or_insert((now, 0));
// Если интервал истек, сбрасываем счетчик
if now.duration_since(entry.0) > self.window {
*entry = (now, 1);
return true;
}
// Увеличиваем счетчик
entry.1 += 1;
// Проверяем, не превышен ли лимит
entry.1 <= self.max_requests
}
}
// Middleware для ограничения частоты запросов
async fn rate_limit_middleware(
Extension(limiter): Extension<Arc<RateLimiter>>,
req: Request<Body>,
next: Next<Body>,
) -> Response {
// Получаем IP клиента (в реальном приложении учитывайте прокси)
let ip = req
.extensions()
.get::<IpAddr>()
.cloned()
.unwrap_or("127.0.0.1".parse().unwrap());
// Проверяем лимит
if limiter.check(ip) {
next.run(req).await
} else {
// Если лимит превышен, возвращаем ошибку 429
(
StatusCode::TOO_MANY_REQUESTS,
"Слишком много запросов. Пожалуйста, подождите.",
)
.into_response()
}
}
// Создаем лимитер: 100 запросов в минуту
let rate_limiter = Arc::new(RateLimiter::new(
100,
Duration::from_secs(60),
));
let app = Router::new()
.route("/api", get(handler))
// Добавляем middleware ограничения частоты
.layer(middleware::from_fn_with_state(
rate_limiter.clone(),
|state, req, next| rate_limit_middleware(Extension(state), req, next)
))
// Добавляем лимитер в расширения
.layer(Extension(rate_limiter));
Создание универсального middleware
Для создания переиспользуемого middleware с конфигурацией можно использовать паттерн Builder:
use axum::{
middleware::{self, Next},
response::IntoResponse,
Router,
body::Body,
http::{Request, StatusCode, HeaderValue, header},
};
use std::collections::HashMap;
// Конфигурация заголовков
struct SecurityHeadersConfig {
headers: HashMap<header::HeaderName, HeaderValue>,
}
impl Default for SecurityHeadersConfig {
fn default() -> Self {
let mut headers = HashMap::new();
// Стандартные заголовки безопасности
headers.insert(
header::CONTENT_SECURITY_POLICY,
HeaderValue::from_static("default-src 'self'"),
);
headers.insert(
header::X_FRAME_OPTIONS,
HeaderValue::from_static("DENY"),
);
headers.insert(
header::X_CONTENT_TYPE_OPTIONS,
HeaderValue::from_static("nosniff"),
);
headers.insert(
header::STRICT_TRANSPORT_SECURITY,
HeaderValue::from_static("max-age=31536000; includeSubDomains"),
);
Self { headers }
}
}
impl SecurityHeadersConfig {
// Создает новую конфигурацию
fn new() -> Self {
Default::default()
}
// Добавляет или заменяет заголовок
fn with_header<K, V>(mut self, key: K, value: V) -> Self
where
K: Into<header::HeaderName>,
V: TryInto<HeaderValue>,
V::Error: std::fmt::Debug,
{
self.headers.insert(
key.into(),
value.try_into().expect("Недопустимое значение заголовка"),
);
self
}
// Удаляет заголовок
fn without_header<K>(mut self, key: K) -> Self
where
K: Into<header::HeaderName>,
{
self.headers.remove(&key.into());
self
}
// Создает middleware из конфигурации
fn layer(self) -> axum::middleware::from_fn::FromFn<impl Fn(Request<Body>, Next<Body>) -> impl std::future::Future<Output = impl IntoResponse> + Clone> {
let headers = self.headers;
middleware::from_fn(move |req, next| {
let headers = headers.clone();
async move {
let mut response = next.run(req).await;
// Добавляем все заголовки безопасности
for (key, value) in headers.iter() {
response.headers_mut().insert(key, value.clone());
}
response
}
})
}
}
// Использование
let security_headers = SecurityHeadersConfig::new()
// Настраиваем CSP для разрешения скриптов с CDN
.with_header(
header::CONTENT_SECURITY_POLICY,
"default-src 'self'; script-src 'self' https://cdn.example.com",
)
// Отключаем X-Frame-Options, если не нужен
.without_header(header::X_FRAME_OPTIONS);
let app = Router::new()
.route("/", get(handler))
// Применяем middleware
.layer(security_headers.layer());
Тестирование middleware
use axum::{
body::{Body, BoxBody},
Router,
middleware::{self, Next},
http::{Request, Response, StatusCode},
routing::get,
};
use tower::ServiceExt;
use http_body_util::BodyExt;
// Middleware для проверки авторизации
async fn auth_middleware(
req: Request<Body>,
next: Next<Body>,
) -> Response<BoxBody> {
if let Some(auth_header) = req.headers().get("Authorization") {
if auth_header == "Bearer valid_token" {
return next.run(req).await;
}
}
Response::builder()
.status(StatusCode::UNAUTHORIZED)
.body(BoxBody::default())
.unwrap()
}
// Тестовый обработчик
async fn test_handler() -> &'static str {
"Авторизован"
}
// Тесты
#[tokio::test]
async fn test_auth_middleware() {
// Создаем приложение с middleware
let app = Router::new()
.route("/protected", get(test_handler))
.layer(middleware::from_fn(auth_middleware));
// Тест без токена
let response = app
.clone()
.oneshot(Request::builder()
.uri("/protected")
.body(Body::empty())
.unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
// Тест с неверным токеном
let response = app
.clone()
.oneshot(Request::builder()
.uri("/protected")
.header("Authorization", "Bearer invalid_token")
.body(Body::empty())
.unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::UNAUTHORIZED);
// Тест с правильным токеном
let response = app
.clone()
.oneshot(Request::builder()
.uri("/protected")
.header("Authorization", "Bearer valid_token")
.body(Body::empty())
.unwrap())
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"Авторизован");
}
Лучшие практики применения middleware
1. Порядок middleware
Располагайте middleware в правильном порядке:
- Сначала общие middleware (логирование, трассировка)
- Затем middleware уровня безопасности (CORS, ограничение частоты)
- Затем бизнес-логика (аутентификация, авторизация)
let app = Router::new()
.route("/", get(handler))
// Бизнес-логика
.layer(middleware::from_fn(auth_middleware))
// Безопасность
.layer(CorsLayer::permissive())
.layer(middleware::from_fn(rate_limit_middleware))
// Общая функциональность
.layer(TraceLayer::new_for_http())
.layer(middleware::from_fn(timing_middleware));
2. Разделение middleware по ответственности
Создавайте middleware для конкретных задач, следуя принципу единственной ответственности:
// Плохо: один middleware делает много задач
async fn complex_middleware(req: Request<Body>, next: Next<Body>) -> Response {
// Логирование
println!("Запрос: {:?}", req.uri());
// Авторизация
if let Some(auth_header) = req.headers().get("Authorization") {
// ...
}
// Сбор метрик
// ...
next.run(req).await
}
// Хорошо: разделение на отдельные middleware
async fn logging_middleware(req: Request<Body>, next: Next<Body>) -> Response {
println!("Запрос: {:?}", req.uri());
next.run(req).await
}
async fn auth_middleware(req: Request<Body>, next: Next<Body>) -> Response {
// Логика авторизации
// ...
next.run(req).await
}
async fn metrics_middleware(req: Request<Body>, next: Next<Body>) -> Response {
// Сбор метрик
// ...
next.run(req).await
}
3. Использование расширений для общих данных
// Создаем структуры для общих данных
#[derive(Clone)]
struct DatabasePool(/* ... */);
#[derive(Clone)]
struct AppConfig {
debug: bool,
rate_limit: usize,
}
// Применяем как расширения
let db_pool = DatabasePool(/* ... */);
let config = AppConfig {
debug: true,
rate_limit: 100,
};
let app = Router::new()
.route("/", get(handler))
// Добавляем расширения, доступные во всех middleware и обработчиках
.layer(Extension(db_pool))
.layer(Extension(config));
4. Применение middleware к группам маршрутов
// Создаем группы маршрутов с разными middleware
let api_routes = Router::new()
.route("/users", get(users_handler))
.route("/posts", get(posts_handler))
// API-специфичные middleware
.layer(middleware::from_fn(api_auth_middleware))
.layer(CorsLayer::permissive());
let admin_routes = Router::new()
.route("/dashboard", get(dashboard_handler))
.route("/settings", get(settings_handler))
// Middleware для админов
.layer(middleware::from_fn(admin_auth_middleware));
let public_routes = Router::new()
.route("/", get(home_handler))
.route("/about", get(about_handler));
// Объединяем маршруты
let app = Router::new()
.nest("/api", api_routes)
.nest("/admin", admin_routes)
.nest("/", public_routes)
// Общие middleware
.layer(TraceLayer::new_for_http());
Заключение
Axum предоставляет мощный и гибкий механизм middleware, основанный на экосистеме tower. Основные преимущества:
- Совместимость с существующими middleware из экосистемы tower
- Возможность создания собственных middleware разной сложности
- Гибкая настройка и комбинирование middleware
- Типобезопасность благодаря сильной системе типов Rust
При правильном использовании middleware в Axum помогает создавать модульные, поддерживаемые и производительные веб-приложения, избегая дублирования кода и соблюдая принцип разделения ответственности.