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

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

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

Начать курс

Что такое Axum и зачем он нужен?

Axum — это современный, эргономичный и производительный веб-фреймворк для языка Rust, разработанный командой Tokio. Он построен на основе популярных крейтов tokio, tower и hyper, что обеспечивает высокую производительность и асинхронность "из коробки".

Основная цель Axum — предоставить разработчикам интуитивно понятный API для создания веб-приложений и API, сохраняя при этом всю мощь и безопасность Rust. В отличие от более "низкоуровневых" решений, Axum абстрагирует сложность работы с HTTP-протоколом, позволяя сосредоточиться на бизнес-логике.

Axum идеально подходит для:

  • Создания RESTful API и микросервисов
  • Разработки веб-приложений с серверным рендерингом
  • Построения прокси-серверов и middleware
  • Реализации WebSocket-соединений
  • Любых сценариев, где требуется высокая производительность и надежность

Установка Axum

Поскольку Rust — это компилируемый язык, установка происходит через менеджер пакетов Cargo. Добавьте следующие зависимости в ваш файл Cargo.toml:

[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tower-http = { version = "0.5", features = ["cors", "trace"] }
tracing = "0.1"
tracing-subscriber = "0.3"

После этого выполните команду cargo build, чтобы скачать и скомпилировать зависимости.

Быстрый старт: первое приложение на Axum

Создайте файл src/main.rs и напишите простейший HTTP-сервер:

use axum::{
    routing::get,
    Router,
};
use std::net::SocketAddr;

#[tokio::main]
async fn main() {
    // Создаем маршрутизатор
    let app = Router::new()
        .route("/", get(root_handler))
        .route("/hello/:name", get(hello_handler));

    // Запускаем сервер
    let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
    println!("Сервер запущен на {}", addr);
    
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn root_handler() -> &'static str {
    "Добро пожаловать на главную страницу!"
}

async fn hello_handler(axum::extract::Path(name): axum::extract::Path) -> String {
    format!("Привет, {}!", name)
}

Запустите сервер командой cargo run и откройте браузер по адресу http://localhost:3000/hello/Мир.

Основные методы и функции Axum

1. Router::new()

Описание: Создает новый пустой маршрутизатор. Это основная точка входа для конфигурации вашего приложения.

Параметры: Нет.

Пример:

use axum::Router;

let app: Router = Router::new();

2. Router::route()

Описание: Добавляет маршрут к указанному пути с заданным HTTP-методом и обработчиком.

Параметры:

  • path: &str — путь маршрута (может содержать динамические сегменты, например /users/:id)
  • method_router: MethodRouter — результат вызова get(), post(), put(), delete() и т.д.

Пример:

use axum::{routing::get, Router};

async fn users_list() -> &'static str { "Список пользователей" }
async fn create_user() -> &'static str { "Создание пользователя" }

let app = Router::new()
    .route("/users", get(users_list).post(create_user));

3. routing::get(), post(), put(), delete()

Описание: Создают обработчик для соответствующего HTTP-метода.

Параметры:

  • handler: Handler — асинхронная функция, которая обрабатывает запрос

Пример:

use axum::routing::{get, post, put, delete};

async fn get_user() -> &'static str { "GET" }
async fn update_user() -> &'static str { "PUT" }
async fn delete_user() -> &'static str { "DELETE" }

let app = Router::new()
    .route("/users/:id", get(get_user).put(update_user).delete(delete_user));

4. axum::extract::Path

Описание: Извлекает параметры пути из URL. Поддерживает как одиночные значения, так и структуры.

Параметры: Generic параметр, определяющий тип извлекаемого значения.

Пример:

use axum::extract::Path;
use serde::Deserialize;

#[derive(Deserialize)]
struct UserParams {
    id: u32,
    action: String,
}

async fn user_action(Path(params): Path) -> String {
    format!("Действие {} для пользователя {}", params.action, params.id)
}

// URL: /users/42/edit
// Результат: "Действие edit для пользователя 42"

5. axum::extract::Query

Описание: Извлекает параметры запроса (query string) из URL.

Параметры: Generic параметр, определяющий структуру с полями, соответствующими параметрам запроса.

Пример:

use axum::extract::Query;
use serde::Deserialize;

#[derive(Deserialize)]
struct SearchParams {
    q: String,
    page: Option,
}

async fn search(Query(params): Query) -> String {
    let page = params.page.unwrap_or(1);
    format!("Поиск '{}', страница {}", params.q, page)
}

// URL: /search?q=rust&page=2
// Результат: "Поиск 'rust', страница 2"

6. axum::extract::Json

Описание: Извлекает JSON-тело запроса и десериализует его в указанный тип.

Параметры: Generic параметр, определяющий тип данных для десериализации.

Пример:

use axum::extract::Json;
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct CreateUser {
    name: String,
    email: String,
}

#[derive(Serialize)]
struct UserResponse {
    id: u32,
    name: String,
}

async fn create_user(Json(payload): Json) -> Json {
    // Логика создания пользователя
    Json(UserResponse {
        id: 1,
        name: payload.name,
    })
}

7. axum::response::Json

Описание: Создает JSON-ответ с автоматической сериализацией данных.

Параметры: Generic параметр, реализующий Serialize.

Пример:

use axum::response::Json;
use serde::Serialize;

#[derive(Serialize)]
struct ApiResponse {
    success: bool,
    message: String,
}

async fn api_endpoint() -> Json {
    Json(ApiResponse {
        success: true,
        message: "Операция выполнена успешно".to_string(),
    })
}

8. axum::http::StatusCode

Описание: Позволяет возвращать HTTP-статус коды вместе с ответом.

Параметры: Константы из модуля axum::http::StatusCode.

Пример:

use axum::{http::StatusCode, response::IntoResponse};

async fn not_found() -> impl IntoResponse {
    (StatusCode::NOT_FOUND, "Ресурс не найден")
}

async fn created() -> impl IntoResponse {
    (StatusCode::CREATED, "Ресурс создан")
}

9. axum::middleware::from_fn

Описание: Создает middleware из пользовательской функции для обработки запросов до или после основного обработчика.

Параметры:

  • middleware_fn: fn — асинхронная функция, принимающая Request и Next

Пример:

use axum::{
    middleware,
    routing::get,
    Router,
    http::Request,
    response::Response,
};

async fn auth_middleware(
    req: Request,
    next: middleware::Next,
) -> Response {
    // Проверка авторизации
    if let Some(auth_header) = req.headers().get("Authorization") {
        // Продолжаем обработку
        next.run(req).await
    } else {
        // Возвращаем 401
        (axum::http::StatusCode::UNAUTHORIZED, "Требуется авторизация").into_response()
    }
}

let app = Router::new()
    .route("/protected", get(protected_handler))
    .layer(middleware::from_fn(auth_middleware));

10. Router::with_state()

Описание: Позволяет передавать общее состояние (state) во все обработчики маршрутов.

Параметры:

  • state: S — произвольное значение, которое будет доступно в обработчиках

Пример:

use axum::{extract::State, routing::get, Router};
use std::sync::Arc;

struct AppState {
    db_pool: String,
}

async fn get_users(State(state): State>) -> String {
    format!("Подключение к БД: {}", state.db_pool)
}

let state = Arc::new(AppState {
    db_pool: "postgres://localhost/mydb".to_string(),
});

let app = Router::new()
    .route("/users", get(get_users))
    .with_state(state);

Реальный практический пример: REST API для управления задачами

Создадим полноценное REST API для управления списком задач (Todo List) с использованием Axum:

use axum::{
    extract::{Path, Query, State},
    http::StatusCode,
    response::Json,
    routing::{delete, get, post, put},
    Router,
};
use serde::{Deserialize, Serialize};
use std::sync::{Arc, Mutex};
use std::collections::HashMap;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Task {
    id: String,
    title: String,
    completed: bool,
}

#[derive(Debug, Deserialize)]
struct CreateTask {
    title: String,
}

#[derive(Debug, Deserialize)]
struct UpdateTask {
    title: Option,
    completed: Option,
}

type Db = Arc>>;

#[tokio::main]
async fn main() {
    let db: Db = Arc::new(Mutex::new(HashMap::new()));

    let app = Router::new()
        .route("/tasks", get(list_tasks).post(create_task))
        .route("/tasks/:id", get(get_task).put(update_task).delete(delete_task))
        .with_state(db);

    let addr = "0.0.0.0:3000".parse().unwrap();
    println!("Сервер запущен на {}", addr);
    
    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

async fn list_tasks(State(db): State) -> Json> {
    let tasks = db.lock().unwrap();
    Json(tasks.values().cloned().collect())
}

async fn create_task(
    State(db): State,
    Json(payload): Json,
) -> (StatusCode, Json) {
    let task = Task {
        id: Uuid::new_v4().to_string                    

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