Волга
Главная
Agent Skill
API Docs
GitHub
  • English
  • Русский
Главная
Agent Skill
API Docs
GitHub
  • English
  • Русский
  • Главная
  • Старт

    • Быстрый старт
    • Обработчики
    • Параметры маршрута
    • Параметры запроса
    • Группировка маршрутов
  • Запросы и ответы

    • Заголовки (Headers)
    • Работа с JSON
    • Работа с Form Data
    • Работа с файлами
    • Multipart-ответы
    • Сырое тело запроса
    • Cookies
    • Валидация запросов
  • Middleware и инфраструктура

    • Основы Middleware
    • Пользовательские Middleware
    • Параметризованные Middleware
    • Сжатие ответов
    • Распаковка запросов
    • CORS (Cross-Origin Resource Sharing)
    • Статические файлы
    • Rate Limiting
    • Файлы конфигурации
    • OpenAPI
  • Безопасность и доступ

    • Аутентификация и авторизация
    • OAuth 2.1 и OpenID Connect
    • OAuth 2.1 Клиент
    • Гранты «сервис-сервис»
    • DPoP — токены, привязанные к отправителю
  • Надежность и наблюдаемость

    • Центральный обработчик ошибок
    • Логгирование и Трассировка
    • Отмена запросов
    • Плавное завершение работы
  • Протоколы и realtime

    • Версии и методы HTTP
    • HTTPS
    • WebSockets
    • Server-Sent Events (SSE)
  • Продвинутые паттерны

    • Внедрение Зависимостей
    • Пользовательская обработка методов HEAD, OPTIONS и TRACE
  • Agent Skill

WebSockets

Волга обеспечивает встроенную поддержку WebSockets с использованием единого гибкого API. Это позволяет осуществлять бесшовную обработку соединений на всех уровнях, от установления соединения до обработки отдельных сообщений, с возможностью внедрения зависимостей или доступа к метаданным HTTP.

Переключение между WebSockets и WebSocket-over-HTTP/2

При работе под HTTP/2, Волга использует WebSocket-over-HTTP/2 по-умолчанию и переключается на WebSockets, когда доступен только HTTP/1. Это поведение, так же, можно настроить с помощью флагов.

WebSockets

[dependencies]
volga = { version = "...", features = ["ws"] }

WebSocket-over-HTTP/2

[dependencies]
volga = { version = "...", features = ["http2", "ws"] }

Простой сервер

После обновления Cargo.toml с флагом ws вы можете реализовать базовый обработчик сообщений с помощью метода map_msg(). Следующий пример отвечает отформатированной строкой, содержащей полученное сообщение:

use volga::App;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    // Простой обработчик входящих сообщений
    app.map_msg("/ws", |msg: String| async move {
        format!("Received: {}", msg)
    });
    
    app.run().await
}

Обработчик сообщений, которому нечего ждать, начиная с v0.11.0 может возвращать ответ напрямую:

app.map_msg("/ws", |msg: String| format!("echo: {msg}"));

Аргументы после сообщения читают запрос, открывший соединение, включая параметры пути, — начиная с 0.14.0, когда map_ws и map_msg стали передавать своему обработчику параметры его маршрута:

app.map_msg("/ws/{room}", |msg: String, room: String| format!("[{room}] {msg}"));

JSON-сообщения

Обработчик сообщений может принимать и возвращать Json<T>. Json<T> принимается из текстового или бинарного фрейма, а отправляется текстовым фреймом — и ответ обработчика map_msg, и WebSocket::send, и WsSink::send, — поэтому браузерный клиент читает его из event.data как строку и передаёт в JSON.parse:

use volga::{App, Json};
use serde::{Deserialize, Serialize};

#[derive(Deserialize)]
struct Request {
    text: String,
}

#[derive(Serialize)]
struct Reply {
    echo: String,
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    app.map_msg("/ws", |msg: Json<Request>| {
        Json(Reply { echo: msg.text.clone() })
    });

    app.run().await
}

Собственные типы сообщений

Сообщение — это всё, что реализует TryFrom<Message>, а ответ — всё, что реализует TryInto<Message>. Начиная с 0.12.0 ошибкой их преобразования может быть любой тип, который превращается в Error из Volga, — serde_json::Error, пара (StatusCode, E), ваш собственный тип, реализующий IntoError, — а не только сам Error. То же верно для WebSocket::on_msg, WebSocket::recv и send, WsStream::recv и WsSink::send.

use serde::Deserialize;
use volga::{App, ws::Message};

#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
enum Command {
    Ping,
    Echo { text: String },
}

impl TryFrom<Message> for Command {
    type Error = serde_json::Error;

    fn try_from(msg: Message) -> Result<Self, Self::Error> {
        serde_json::from_slice(&msg.into_inner().into_data())
    }
}

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    // {"type":"ping"}                  -> pong
    // {"type":"echo","text":"hello"}   -> hello
    app.map_msg("/ws", |cmd: Command| match cmd {
        Command::Ping => "pong".to_string(),
        Command::Echo { text } => text,
    });

    // `Message` тоже годится как ответ: это эхо оставляет текстовые кадры текстовыми, а бинарные — бинарными
    app.map_msg("/echo", |msg: Message| msg);

    app.run().await
}

Совет

Кадр, который не преобразуется в тип сообщения обработчика, пропускается, а соединение остаётся открытым; с включённой фичей tracing это логируется как ошибка. Если клиент должен узнать, что его сообщение отклонено, принимайте тип, подходящий для любого кадра, — Message, Bytes — и отвечайте ошибкой сами, либо читайте сокет через recv(), который отдаёт ошибку преобразования вам.

Это простые примеры. Чтобы получить больше контроля над конкретным соединением, вы можете воспользоваться другим методом - map_ws().

use volga::{App, ws::WebSocket};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    // Простой обработчик WebSocket
    app.map_ws("/ws", |mut ws: WebSocket| async move {
        // Делаем что-нибудь с соединением

        ws.on_msg(|msg: String| async move {
            // Делаем что-нибудь с сообщением

            format!("Received: {}", msg)
        }).await;
    });
    
    app.run().await
}

Данный пример работает аналогично первому, но обеспечивает больший контроль над соединением.

Для продвинутых вариантов использования вы можете разделить WebSocket на отдельные компоненты отправителя и получателя с помощью функции split():

use volga::{App, ws::{WebSocket, WsEvent}};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    // Простой обработчик WebSocket
    app.map_ws("/ws", |ws: WebSocket| async move {
        // Разделяем сокет на отправителя и получателя, которые можно использовать отдельно
        let (mut sender, mut receiver) = ws.split();

        tokio::spawn(async move {
            // `Message: TryFrom<&str>`, so the string goes in as it is
            let _ = sender.send("Hello from WebSockets server!").await;
        });

        tokio::spawn(async move {
            while let Some(Ok(msg)) = receiver.recv::<String>().await {
                match msg {
                    WsEvent::Data(msg) => println!("received: {msg}"),
                    WsEvent::Close(frame) => println!("close: {frame:?}"),
                    _ => (),
                }
            }
        });
    });
    
    app.run().await
}

Этот пример отправляет одно сообщение при подключении, а затем, логгирует все входящие сообщения.

Для полного контроля, например, для настройки подключения или указания некоторых подпротоколов есть еще один метод - map_conn(), вы можете использовать его следующим образом:

use volga::{App, ws::{WebSocketConnection, WebSocket}};

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    app.map_conn("/ws", handle);

    app.run().await
}

async fn handle(conn: WebSocketConnection) -> HttpResult {
    // Здесь можно настроить соединение и извлечь что-нибудь из DI или метаданных HTTP.
    conn.with_protocols(["foo-ws"]).on(handle_socket)
}

async fn handle_socket(mut ws: WebSocket) {
    ws.on_msg(handle_message).await;
}

async fn handle_message(msg: String) -> String {
    format!("Received: {msg}")
}

Внедрение зависимостей

Вы можете внедрить любую зависимость из контейнера DI, на любом уровне, используя Dc<T> стандартным способом, описанным здесь.

use volga::{App, ws::{WebSocketConnection, WebSocket}};
use std::sync::{Arc, RwLock};

type Counter = Arc<RwLock<i32>>;

#[tokio::main]
async fn main() -> std::io::Result<()> {
    let mut app = App::new();

    app.add_singleton(Counter::default());
    app.map_conn("/ws", handle);

    app.run().await
}

async fn handle(conn: WebSocketConnection, counter: Dc<Counter>) -> HttpResult {
    conn.with_protocols(["foo-ws"]).on(|ws| handle_socket(ws, counter))
}

async fn handle_socket(mut ws: WebSocket, counter: Dc<Counter>) {
    ws.on_msg(move |msg: String| handle_message(msg, counter.clone())).await;
}

async fn handle_message(msg: String, counter: Dc<Counter>) -> String {
    let mut value = counter.write().expect("Failed to lock counter");
    *value += 1;
    format!("Received: {msg}; Message #{value}")
}

Полный пример можно найти здесь.

Последнее обновление: 20.10.2026, 15:26
Prev
HTTPS
Next
Server-Sent Events (SSE)