Главная /
Курсы /
Rust: бэкенд на Axum /
Глава 1. Введение в Axum /
Что такое Axum и его особенности
# Глава 1.1. Что такое Axum и его особенности
Axum — это веб-фреймворк для создания HTTP-сервисов на Rust. Его разрабатывает [команда Tokio](https://github.com/tokio-rs). Tokio — самый популярный асинхронный рантайм для Rust, то есть библиотека, которая выполняет асинхронный код и берёт на себя работу с сетью. Сам Axum построен поверх [hyper](https://habr.com/ru/articles/917936/) — низкоуровневой библиотеки, которая реализует протокол HTTP: разбирает входящие запросы и формирует ответы.
Axum отвечает за то, что происходит с запросом после его разбора: за маршрутизацию, то есть выбор функции-обработчика по пути и методу запроса, за извлечение данных из запроса и за формирование ответа. Всё остальное он делегирует другим библиотекам экосистемы Tokio.
## Почему Axum
Axum создан командой Tokio и тесно интегрирован с экосистемой вокруг этого проекта. Он не реализует HTTP-сервер целиком сам: работа распределена между несколькими библиотеками, и каждая отвечает за свой слой:
| Крейт | Слой | За что отвечает |
| ------- | ------------------- | ---------------------------------------------------------------------------- |
| `tokio` | Асинхронный рантайм | Выполнение асинхронных задач, работа с сетью и таймерами |
| `hyper` | HTTP | Реализация протоколов HTTP/1.1 и HTTP/2 |
| `tower` | Промежуточные слои | Инфраструктура, служебные слои и отказоустойчивость сервисов |
| `axum` | Веб-фреймворк | Маршрутизация запросов, их обработчики и извлечение данных из запросов |
Промежуточный слой (middleware) — это код, который выполняется до или после обработчика для каждого запроса. Например, он может записать запрос в лог, проверить авторизацию или ограничить время ответа.
Это важно для понимания: Axum не реализует весь HTTP-стек самостоятельно, а использует готовые компоненты экосистемы. Его задача — предоставить удобные абстракции для написания REST API. При этом промежуточные слои из `tower` можно использовать и без Axum, а при необходимости можно работать с HTTP на более низком уровне через `hyper`.
## Плюсы Axum
- Большая экосистема. Axum интегрирован с экосистемой Tokio, поэтому его легко сочетать с нужными библиотеками. Например, промежуточные слои из `tower` работают с Axum без адаптеров.
- Меньше ошибок во время выполнения. Многие ошибки обнаруживаются на этапе компиляции благодаря системе типов Rust.
- Мало магии. Axum не прячет устройство приложения за большим количеством макросов. Основные части сервера представлены обычными структурами, функциями и трейтами.
## Альтернативы
- **Actix Web** — главный конкурент Axum и самый зрелый фреймворк в экосистеме. Он долгие годы возглавляет бенчмарки, обладает богатой функциональностью, объявляет маршруты через макросы и построен на акторной модели. Из минусов — собственная экосистема. Кроме того, репутация проекта пострадала из-за большого количества `unsafe` в ранних версиях и конфликта вокруг [мейнтейнера](https://habr.com/ru/articles/484436/). Сейчас код давно вычищен, но осадок в интернете остался.
- **Rocket** — более дружелюбный к новичкам фреймворк с максимумом магии на макросах, похожий на Flask из Python. Хорошо подходит для обучения и прототипов. Из минусов: он долгое время требовал ночную сборку компилятора, релизы выходят редко, экосистема заметно меньше, а магия макросов мешает, когда нужно нестандартное поведение.
- **Loco** — фреймворк в духе Ruby on Rails, построенный поверх Axum и рассчитанный на создание приложения целиком, а не только API. Он предоставляет больше готовой инфраструктуры и задаёт структуру приложения, но пока недостаточно зрелый для промышленной эксплуатации.
## Первое приложение
Создаём проект:
```bash
cargo new hello-axum
cd hello-axum
```
Добавляем зависимости:
```bash
cargo add axum
cargo add tokio --features macros,rt-multi-thread
```
Tokio разбит на отключаемые части — фичи (features), чтобы в сборку попадало только то, что действительно используется. Нам нужны две фичи: `macros` добавляет атрибут `#[tokio::main]`, а `rt-multi-thread` подключает многопоточный планировщик задач, который этот атрибут использует по умолчанию. Без `rt-multi-thread` проект не соберётся:
```text
error: The default runtime flavor is `multi_thread`,
but the `rt-multi-thread` feature is disabled.
```
Работу с сетью отдельно подключать не нужно: её включает сам Axum.
Теперь создадим HTTP-сервер:
```rust
// src/main.rs
use axum::{routing::get, Router};
use tokio::net::TcpListener;
#[tokio::main]
async fn main() {
// запросы GET / будет обрабатывать функция hello
let router = Router::new().route("/", get(hello));
let listener = TcpListener::bind("127.0.0.1:3000")
.await
.unwrap();
println!("Слушаем на http://{}", listener.local_addr().unwrap());
axum::serve(listener, router).await.unwrap();
}
async fn hello() -> &'static str {
"Hello, World!"
}
```
Разберём, что здесь происходит.
Функция `main` — точка входа в программу, и асинхронной она быть не может. Вызов асинхронной функции не выполняет её код, а лишь возвращает `Future` — описание работы, которую только предстоит сделать. Чтобы эта работа началась, нужен асинхронный рантайм, который будет её выполнять. В стандартной библиотеке Rust рантайма нет, и это сделано намеренно: серверу, микроконтроллеру и браузерному приложению нужны разные рантаймы, поэтому язык оставляет выбор разработчику. Точку входа вызывает операционная система, и она должна быть обычной функцией, поэтому объявление `async fn main` без атрибута не скомпилируется. Атрибут `#[tokio::main]` превращает нашу асинхронную `main` в обычную, которая создаёт рантайм Tokio и запускает в нём код. При желании то же самое можно написать вручную через [`tokio::runtime::Builder`](https://docs.rs/tokio/latest/tokio/runtime/struct.Builder.html).
`Router` отвечает за распределение запросов: он хранит таблицу соответствий между маршрутами и обработчиками. Дальше в курсе обработчик запроса будем называть хендлером. Метод `route` принимает путь и указывает, какая функция обработает запросы к нему, а функция `get` уточняет, что это касается только HTTP-метода GET. Функция `hello` передаётся без круглых скобок, потому что мы не вызываем её сами, а передаём Axum, чтобы он вызывал её на каждый подходящий запрос.
`TcpListener` отвечает за сетевое подключение: он занимает адрес `127.0.0.1:3000` и принимает входящие соединения. Обратите внимание, что мы импортируем `tokio::net::TcpListener`, а не `std::net::TcpListener` из стандартной библиотеки. Версия из стандартной библиотеки на время ожидания клиента блокирует поток операционной системы: пока соединение не придёт, поток больше ничего не делает. В асинхронном приложении это недопустимо, потому что на одном потоке рантайм выполняет множество задач, и заблокированный поток остановил бы их все. Версия из Tokio на время ожидания возвращает управление рантайму, и тот продолжает выполнять другие задачи.
`axum::serve` запускает асинхронный HTTP-сервер, который связывает сетевой адрес с обработчиками запросов. Он принимает соединения от `TcpListener`, с помощью hyper разбирает пришедшие по ним HTTP-запросы и передаёт их в `Router`, который выбирает подходящий обработчик.
Функция `hello` — это обработчик маршрута. Она возвращает строку, а Axum сам превращает её в HTTP-ответ и выставляет нужные заголовки. Как это устроено, разберём в главе 1.4.
Метод `unwrap` извлекает значение из `Result` или завершает программу с ошибкой, например если порт уже занят. Для учебного примера это допустимо, но в реальном приложении ошибки обрабатывают явно. Подробнее об обработке ошибок поговорим в главе 1.7.
Запускаем:
```bash
cargo run
```
Проверяем:
```bash
$ curl -i http://127.0.0.1:3000/
HTTP/1.1 200 OK
content-type: text/plain; charset=utf-8
content-length: 13
date: ...
Hello, World!
```
Сервер ответил статусом `200 OK` и вернул нашу строку. Заголовки `content-type` и `content-length` мы нигде не указывали: их выставил Axum.
Мы запустили сервер с одним маршрутом. Почти каждый реальный обработчик принимает и возвращает данные в формате JSON, поэтому в следующей главе разберёмся, как Rust-структуры превращаются в JSON и обратно.
Наша группа в telegram. Задавайте вопросы и общайтесь.
Задонатить. Поддержите развитие площадки.