Какие наиболее часто используемые встроенные extractors в Axum (Path, Query, Json, State и др.)?
Основные экстракторы: Path (сегменты URL), Query (query string), Json (тело запроса), State (shared state через with_state). Из axum-extra: TypedHeader для типизированных заголовков. Json должен быть последним аргументом, так как потребляет body.
Встроенные экстракторы Axum
Axum предоставляет набор готовых экстракторов в модуле axum::extract. Каждый отвечает за конкретный источник данных в HTTP-запросе.
Path — параметры пути
Извлекает именованные сегменты URL. Тип должен реализовывать serde::Deserialize.
use axum::extract::Path;
// Одиночный параметр
async fn get_user(Path(id): Path<u32>) -> String {
format!("User {id}")
}
// Несколько параметров через кортеж или структуру
async fn get_comment(
Path((post_id, comment_id)): Path<(u32, u32)>,
) -> String {
format!("Post {post_id}, comment {comment_id}")
}
// Маршруты
// .route("/users/:id", get(get_user))
// .route("/posts/:post_id/comments/:comment_id", get(get_comment))
Query — параметры строки запроса
Парсит query string в структуру. При отсутствии поля с #[serde(default)] возвращает 422.
use axum::extract::Query;
use serde::Deserialize;
#[derive(Deserialize)]
struct SearchParams {
q: String,
#[serde(default = "default_page")]
page: u32,
#[serde(default = "default_size")]
size: u32,
}
fn default_page() -> u32 { 1 }
fn default_size() -> u32 { 20 }
async fn search(Query(params): Query<SearchParams>) -> String {
format!("Search '{}', page {}, size {}", params.q, params.page, params.size)
}
Json — тело запроса
Десериализует JSON-тело. Требует заголовок Content-Type: application/json. Должен быть последним аргументом обработчика, так как потребляет body.
use axum::{extract::Json, response::IntoResponse, http::StatusCode};
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
struct CreateItem { name: String, price: f64 }
#[derive(Serialize)]
struct Item { id: u32, name: String, price: f64 }
async fn create_item(Json(payload): Json<CreateItem>) -> impl IntoResponse {
let item = Item { id: 1, name: payload.name, price: payload.price };
(StatusCode::CREATED, Json(item))
}
State — разделяемое состояние приложения
Передаёт shared state (DB pool, конфиг, клиент) во все обработчики. Тип должен реализовывать Clone.
use axum::{extract::State, Router, routing::get};
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db_pool: sqlx::PgPool,
config: Arc<Config>,
}
async fn list_users(State(state): State<AppState>) -> impl IntoResponse {
let users = sqlx::query_as::<_, User>("SELECT * FROM users")
.fetch_all(&state.db_pool)
.await
.unwrap();
Json(users)
}
let state = AppState { db_pool, config: Arc::new(config) };
let app = Router::new()
.route("/users", get(list_users))
.with_state(state);
Headers и TypedHeader
Для извлечения конкретных заголовков используется TypedHeader из крейта axum-extra:
use axum_extra::TypedHeader;
use headers::Authorization;
use headers::authorization::Bearer;
async fn auth_handler(
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
) -> String {
format!("Token: {}", auth.token())
}
Другие встроенные экстракторы
axum::extract::Form<T>— application/x-www-form-urlencoded телаaxum::extract::Multipart— multipart/form-data (загрузка файлов)axum::extract::ConnectInfo<SocketAddr>— IP-адрес клиентаaxum::extract::MatchedPath— шаблон пути, совпавший с маршрутомaxum::body::Bytes— сырое тело запросаString— тело как UTF-8 строкаaxum::extract::Request— весь запрос целиком
Подводные камни
Json<T>требует точный заголовокContent-Type: application/json; без него — 415 Unsupported Media Type, что неочевидно при отладке curl-запросов.State<T>требует.with_state(value)при построении Router; если забыть, компилятор выдаст запутанную ошибку об отсутствии реализации трейта.- При использовании вложенных роутеров через
.nest()State нужно добавлять на уровне каждого суб-роутера или передавать черезRouter::with_state()на верхнем уровне. Pathextractor возвращает 400, если тип не соответствует (например, строка вместо u32), но сообщение об ошибке по умолчанию минимально — стоит настраивать кастомный error handler.Queryс обязательным полем без#[serde(default)]вернёт 422 при отсутствующем параметре, что клиент может интерпретировать неверно.Multipartнужно обрабатывать итерационно через.next_field().await; попытка получить все поля сразу не поддерживается.ConnectInfo<SocketAddr>работает только если сервер запущен черезaxum::serve(...).into_make_service_with_connect_info(), иначе паникует.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.