AxumMiddleTechnical
Что такое FromRequest и FromRequestParts в Axum? Когда следует их реализовывать?
FromRequestParts извлекает данные только из заголовков/URI (не потребляет тело), FromRequest может читать тело запроса; первый следует использовать для аутентификации и параметров пути.
Разница между FromRequest и FromRequestParts
Axum разделяет экстракцию данных на два трейта в зависимости от того, нужен ли доступ к телу запроса:
FromRequestParts— работает только сParts(метод, URI, заголовки, extensions). Тело не потребляется. Можно использовать несколько раз, в том числе в middleware.FromRequest— получает полныйRequestвключая тело. Тело можно прочитать только один раз, поэтому в сигнатуре хендлера должен быть не более одного экстрактора, реализующегоFromRequest(напримерJson<T>,Bytes,String).
Реализация FromRequestParts — извлечение JWT из заголовка
use axum::{
async_trait,
extract::FromRequestParts,
http::{request::Parts, StatusCode},
};
struct AuthUser {
user_id: i64,
}
#[async_trait]
impl<S> FromRequestParts<S> for AuthUser
where
S: Send + Sync,
{
type Rejection = (StatusCode, &'static str);
async fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> Result<Self, Self::Rejection> {
let token = parts
.headers
.get("Authorization")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "))
.ok_or((StatusCode::UNAUTHORIZED, "missing token"))?;
let user_id = verify_jwt(token)
.map_err(|_| (StatusCode::UNAUTHORIZED, "invalid token"))?;
Ok(AuthUser { user_id })
}
}
async fn profile(user: AuthUser) -> String {
format!("user_id = {}", user.user_id)
}
Реализация FromRequest — кастомный JSON с валидацией
use axum::{
async_trait,
extract::{FromRequest, Request},
http::StatusCode,
Json,
};
use serde::de::DeserializeOwned;
use validator::Validate;
struct ValidJson<T>(T);
#[async_trait]
impl<T, S> FromRequest<S> for ValidJson<T>
where
T: DeserializeOwned + Validate,
S: Send + Sync,
{
type Rejection = (StatusCode, String);
async fn from_request(req: Request, state: &S) -> Result<Self, Self::Rejection> {
let Json(value) = Json::<T>::from_request(req, state)
.await
.map_err(|e| (StatusCode::BAD_REQUEST, e.to_string()))?;
value.validate()
.map_err(|e| (StatusCode::UNPROCESSABLE_ENTITY, e.to_string()))?;
Ok(ValidJson(value))
}
}
Доступ к State внутри экстрактора
Если экстрактору нужен доступ к состоянию приложения, используйте FromRef:
use axum::extract::{FromRef, FromRequestParts};
#[async_trait]
impl<S> FromRequestParts<S> for AuthUser
where
AppState: FromRef<S>,
S: Send + Sync,
{
type Rejection = StatusCode;
async fn from_request_parts(parts: &mut Parts, state: &S) -> Result<Self, StatusCode> {
let app_state = AppState::from_ref(state);
// используем app_state.jwt_secret для верификации
todo!()
}
}
Подводные камни
- Если реализовать
FromRequestтам, где достаточноFromRequestParts, экстрактор нельзя использовать совместно сJson<T>— они оба потребуют тело и компилятор откажется. async_traitпо-прежнему требуется вплоть до стабилизацииasync fn in traitsв Rust; без него компилятор выдаёт ошибку об object-safety.- Тип
Rejectionдолжен реализовыватьIntoResponse; использованиеStringнапрямую не сработает — оберните в(StatusCode, String). - Порядок параметров в хендлере важен:
FromRequestParts-экстракторы должны идти доFromRequest-экстракторов (Axum проверяет это статически). - Паника внутри
from_requestубьёт worker-поток Tokio; всегда возвращайтеErrвместоpanic!. - Не читайте
bodyдважды: после того как тело потреблено одним экстрактором, другой получит пустые байты без ошибки. - При использовании
axum::middleware::from_fnнельзя напрямую использовать экстракторы — нужно вызыватьParts::extract::<T>()вручную. - Изменения в
Parts::extensionsвнутри экстрактора видны последующим экстракторам — это удобно для цепочки, но может породить неочевидные зависимости между хендлерами.
Common mistakes
- Отвечать определением без production-сценария.
- Не называть runtime boundary, security boundary или failure mode.
- Игнорировать версию API, observability и тестовую проверку.
What the interviewer is testing
- Объясняет механизм своими словами и без выдуманных API.
- Называет реальные риски, диагностику и критерий корректности.
- Связывает ответ с текущей документацией и миграционными ограничениями.