# Глава 1.5. Экстракторы
Экстракторы — это типы, которые извлекают данные из HTTP-запроса и передают их в хендлер в качестве аргументов. Хендлер просто объявляет в сигнатуре, что ему нужно, а Axum сам разбирает запрос. Если извлечь данные не удалось, хендлер не вызывается, а клиент получает ошибку.
## Встроенные экстракторы
| Экстрактор | Что извлекает |
| ----------------- | ---------------------------------------- |
| `Path<T>` | параметры пути `/users/{id}` (глава 1.3) |
| `Query<T>` | параметры строки запроса `/users?page=2` |
| `Json<T>` | тело запроса в формате JSON (глава 1.2) |
| `Form<T>` | тело HTML-формы |
| `HeaderMap` | все заголовки запроса |
| `Method`, `Uri` | HTTP-метод и адрес запроса |
| `String`, `Bytes` | тело запроса как есть |
| `State<T>` | общее состояние приложения (глава 1.6) |
Помимо встроенных экстракторов, есть дополнительные в крейте `axum-extra`, например для типизированных заголовков и cookies.
## Параметры строки запроса
`Query<T>` разбирает параметры после `?` в структуру. Например, так выглядит пагинация:
```rust
use axum::extract::Query;
use serde::Deserialize;
#[derive(Deserialize)]
struct Pagination {
page: Option<u32>,
limit: Option<u32>,
}
async fn list_users(
Query(pagination): Query<Pagination>,
) -> String {
let page = pagination.page.unwrap_or(1);
let limit = pagination.limit.unwrap_or(20);
format!("page {page}, limit {limit}")
}
// /users -> page 1, limit 20
// /users?page=3 -> page 3, limit 20
// /users?page=abc -> 400 Bad Request
```
Если параметр необязательный, сделайте поле типом `Option` или добавьте к нему атрибут `#[serde(default)]`.
## Заголовки
`HeaderMap` даёт доступ ко всем заголовкам запроса:
```rust
use axum::http::{header, HeaderMap};
async fn user_agent(headers: HeaderMap) -> String {
headers
.get(header::USER_AGENT)
.and_then(|value| value.to_str().ok())
.unwrap_or("unknown")
.to_string()
}
```
Значение заголовка — это `HeaderValue`, а не строка, потому что HTTP допускает в заголовках байты, которые не являются корректным UTF-8. Поэтому перед использованием его нужно преобразовать в строку.
## Несколько экстракторов
Хендлер может принимать сразу несколько экстракторов:
```rust
async fn update_user(
Path(id): Path<u64>,
Query(params): Query<Pagination>,
headers: HeaderMap,
Json(user): Json<UpdateUser>, // тело всегда последним
) -> StatusCode {
StatusCode::NO_CONTENT
}
```
Экстракторы делятся на два вида:
1) `FromRequestParts` — читают только части запроса: метод, адрес, заголовки и состояние. Например, `Path`, `Query`, `HeaderMap`, `State`.
2) `FromRequest` — могут читать тело запроса. Например, `Json`, `Form`, `String`, `Bytes`.
## Свой экстрактор
Экстрактор — это любой тип, реализующий `FromRequestParts` или `FromRequest`. Например, вот экстрактор, который достаёт заголовок `User-Agent` и возвращает `400`, если заголовка нет:
```rust
use axum::{
extract::FromRequestParts,
http::{header, request::Parts, StatusCode},
};
struct UserAgent(String);
impl<S: Send + Sync> FromRequestParts<S> for UserAgent {
type Rejection = (StatusCode, &'static str);
async fn from_request_parts(
parts: &mut Parts,
_state: &S,
) -> Result<Self, Self::Rejection> {
parts
.headers
.get(header::USER_AGENT)
.and_then(|value| value.to_str().ok())
.map(|value| UserAgent(value.to_string()))
.ok_or((StatusCode::BAD_REQUEST, "missing user-agent"))
}
}
async fn handler(UserAgent(agent): UserAgent) -> String {
agent
}
```
`Rejection` — это тип ошибки, которую получит клиент, поэтому он должен реализовывать `IntoResponse`.
Свои экстракторы позволяют убрать повторяющийся код из хендлеров. Например, в модуле про аутентификацию мы напишем экстрактор `AuthUser`, который проверяет токен, и хендлер сможет получить текущего пользователя просто как аргумент.
Теперь у нас есть всё, чтобы принять запрос и вернуть ответ. В следующей главе разберём состояние приложения: как передать хендлерам общие данные, например подключение к базе данных.
Наша группа в telegram. Задавайте вопросы и общайтесь.
Задонатить. Поддержите развитие площадки.