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

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

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

Начать курс

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

Warp — это современный, асинхронный и компоновочный (composable) веб-фреймворк для языка Rust, построенный на базе библиотеки hyper и асинхронного рантайма tokio. Его главная философия — использование фильтров (filters), которые комбинируются как строительные блоки для создания сложных маршрутов и обработчиков запросов.

Warp идеально подходит для создания высокопроизводительных веб-серверов, REST API, микросервисов и даже простых веб-приложений. Благодаря строгой типизации Rust и мощной системе фильтров, Warp позволяет избежать многих ошибок на этапе компиляции, обеспечивая безопасность и предсказуемость кода.

Зачем использовать Warp?

  • Производительность: Rust + async/await дают скорость, сравнимую с C/C++.
  • Безопасность: Компилятор Rust проверяет типы и память, а Warp проверяет корректность маршрутов.
  • Компоновочность: Фильтры можно комбинировать, переиспользовать и тестировать изолированно.
  • Минимализм: Нет макросов и магии — всё строится на функциях и замыканиях.
  • Асинхронность из коробки: Поддержка WebSocket, потоковой передачи данных и долгих соединений.

Установка Warp

Warp — это библиотека для Rust, поэтому установка производится через менеджер пакетов cargo. Убедитесь, что у вас установлен Rust (рекомендуется версия 1.70+).

Создайте новый проект:

cargo new my_warp_app
cd my_warp_app

Добавьте зависимости в файл Cargo.toml:

[dependencies]
warp = "0.3"
tokio = { version = "1", features = ["full"] }

Затем выполните сборку:

cargo build

Если вы используете Windows, может потребоваться установить Build Tools for Visual Studio для компиляции.

Быстрый старт: первый сервер на Warp

Создайте простейший HTTP-сервер, который отвечает "Hello, World!" на любой GET-запрос.

use warp::Filter;

#[tokio::main]
async fn main() {
    // Определяем маршрут: GET / -> "Hello, World!"
    let hello = warp::path::end()
        .map(|| "Hello, World!");

    // Запускаем сервер на порту 3030
    warp::serve(hello)
        .run(([127, 0, 0, 1], 3030))
        .await;
}

Запустите сервер командой cargo run и откройте браузер по адресу http://127.0.0.1:3030. Вы увидите сообщение "Hello, World!".

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

Ниже представлены ключевые функции и методы Warp с подробным описанием и примерами.

1. warp::any()

Что делает: Создаёт фильтр, который соответствует любому запросу (метод, путь, заголовки — всё равно). Используется как база для комбинирования.

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

Пример:

use warp::Filter;

let any_route = warp::any()
    .map(|| "This matches any request");

2. warp::path(String)

Что делает: Создаёт фильтр, который соответствует конкретному сегменту пути. Например, warp::path("api") совпадает с /api.

Параметры: path: impl Into<String> — строка пути.

Пример:

use warp::Filter;

let api_route = warp::path("api")
    .map(|| "API endpoint");

3. warp::path::param()

Что делает: Извлекает динамический параметр из пути (например, ID пользователя). Возвращает значение типа String (или можно указать другой тип, реализующий FromStr).

Параметры: Нет (тип выводится из контекста).

Пример:

use warp::Filter;

let user_route = warp::path!("users" / u32)
    .map(|user_id: u32| format!("User ID: {}", user_id));

4. warp::path::end()

Что делает: Фильтр, который требует, чтобы путь был полностью завершён (т.е. не было дополнительных сегментов). Используется для точного совпадения.

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

Пример:

use warp::Filter;

let root = warp::path::end()
    .map(|| "Root endpoint");

5. warp::get() и warp::post()

Что делает: Фильтры, которые ограничивают метод HTTP (GET, POST, PUT, DELETE и т.д.). Комбинируются с другими фильтрами.

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

Пример:

use warp::Filter;

let get_users = warp::get()
    .and(warp::path("users"))
    .map(|| "GET /users");

let create_user = warp::post()
    .and(warp::path("users"))
    .map(|| "POST /users");

6. warp::body::json()

Что делает: Извлекает тело запроса и десериализует его из JSON в указанный тип Rust. Требует, чтобы тип реализовывал serde::Deserialize.

Параметры: Нет (тип выводится из контекста).

Пример:

use warp::Filter;
use serde::Deserialize;

#[derive(Deserialize)]
struct User {
    name: String,
    age: u8,
}

let create_user = warp::post()
    .and(warp::path("users"))
    .and(warp::body::json::<User>())
    .map(|user: User| format!("Created user: {} age {}", user.name, user.age));

7. warp::reply::json()

Что делает: Создаёт HTTP-ответ с телом в формате JSON. Принимает любой тип, реализующий serde::Serialize.

Параметры: value: &T — ссылка на сериализуемое значение.

Пример:

use warp::Filter;
use serde::Serialize;

#[derive(Serialize)]
struct ApiResponse {
    status: String,
    data: Vec<String>,
}

let json_route = warp::path("data")
    .map(|| {
        let response = ApiResponse {
            status: "ok".to_string(),
            data: vec!["item1".to_string(), "item2".to_string()],
        };
        warp::reply::json(&response)
    });

8. warp::query::<T>()

Что делает: Извлекает параметры строки запроса (query string) и десериализует их в структуру. Тип T должен реализовывать serde::Deserialize.

Параметры: Нет (тип выводится).

Пример:

use warp::Filter;
use serde::Deserialize;

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

let search_route = warp::path("search")
    .and(warp::query::<SearchParams>())
    .map(|params: SearchParams| {
        format!("Search for: {}, page: {:?}", params.q, params.page)
    });

9. warp::header::<String>()

Что делает: Извлекает значение указанного HTTP-заголовка. Если заголовок отсутствует, фильтр не срабатывает.

Параметры: name: &'static str — имя заголовка.

Пример:

use warp::Filter;

let auth_route = warp::path("secure")
    .and(warp::header::<String>("authorization"))
    .map(|token: String| format!("Token: {}", token));

10. warp::fs::dir(String)

Что делает: Создаёт фильтр для раздачи статических файлов из указанной директории. Автоматически обрабатывает MIME-типы и кэширование.

Параметры: path: impl Into<PathBuf> — путь к директории.

Пример:

use warp::Filter;

let static_files = warp::path("static")
    .and(warp::fs::dir("./public"));

11. warp::cors()

Что делает: Настраивает CORS (Cross-Origin Resource Sharing) для разрешения запросов с других доменов.

Параметры: Возвращает билдер с методами allow_origin(), allow_methods(), allow_headers().

Пример:

use warp::Filter;

let cors = warp::cors()
    .allow_origin("https://example.com")
    .allow_methods(vec!["GET", "POST"]);

let api = warp::path("api")
    .map(|| "CORS-enabled API")
    .with(cors);

12. warp::ws()

Что делает: Создаёт фильтр для обработки WebSocket-соединений. Возвращает объект WebSocket для отправки/получения сообщений.

Параметры: Нет (обрабатывается через замыкание).

Пример:

use warp::Filter;
use warp::ws::Message;

let ws_route = warp::path("ws")
    .and(warp::ws())
    .map(|ws: warp::ws::Ws| {
        ws.on_upgrade(|socket| async move {
            let (mut tx, mut rx) = socket.split();
            while let Some(msg) = rx.next().await {
                if let Ok(msg) = msg {
                    if msg.is_text() {
                        tx.send(Message::text("Echo: ".to_string() + msg.to_str().unwrap())).await.unwrap();
                    }
                }
            }
        })
    });

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

Создадим полноценный REST API для управления списком задач (Todo list) с поддержкой CRUD операций и хранением данных в памяти.

Полный код:

use std::sync::Arc;
use tokio::sync::Mutex;
use warp::Filter;
use serde::{Serialize, Deserialize};

// Модель задачи
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Task {
    id: u64,
    title: String,
    completed: bool,
}

// Состояние приложения
type Tasks = Arc<Mutex<Vec<Task>>>;

#[tokio::main]
async fn main() {
    let tasks: Tasks = Arc::new(Mutex::new(Vec::new()));

    // POST /tasks - создать задачу
    let create_task = warp::post()
        .and(warp::path("tasks"))
        .and(warp::body::json::<Task>())
        .and(with_state(tasks.clone()))
        .and_then(create_task_handler);

    // GET /tasks - получить все задачи
    let get_tasks = warp::get()
        .and(warp::path("tasks"))
        .and                    

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