Volga
Home
API Docs
GitHub
  • English
  • Русский
Home
API Docs
GitHub
  • English
  • Русский
  • Home
  • Basics

    • Quick Start
    • Route Parameters
    • Query Parameters
    • Route Groups
    • Headers
  • Data Formats

    • Handling JSON
    • Handling Form Data
    • Working with Files
    • Server-Sent Events (SSE)
  • Protocols

    • HTTP/1 and HTTP/2
    • HTTPS
    • WebSockets & WebTransport
  • Advanced

    • Custom Middleware
    • Response Compression
    • Request Decompression
    • Global Error Handling
    • Dependency Injection
    • Tracing & Logging
    • Static Files
    • CORS (Cross-Origin Resource Sharing)
    • Cookies
    • Request cancellation
    • Custom Handling of HEAD, OPTIONS, and TRACE Methods

WebSockets & WebTransport

Volga provides built-in support for both WebSockets and WebTransport using a single, flexible API. This allows seamless connection handling at all levels, from establishing a connection to processing individual messages, with the ability to inject dependencies or access HTTP metadata.

Switching Between WebSockets and WebTransport

If running under HTTP/2, Volga uses WebTransport by default and falls back to WebSockets when only an HTTP/1 connection is available. This behavior can be configured using feature flags.

WebSockets

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

WebTransport

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

Simple Server

After updating Cargo.toml with the ws feature flag, you can implement a basic message handler using the map_msg() method. The following example responds with a formatted string containing the received message:

use volga::App;

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

    // Simple string message handler
    app.map_msg("/ws", |msg: String| async move {
        format!("Received: {}", msg)
    });
    
    app.run().await
}

This is a very simple example, to get more control over a particular connection you may choose another method - map_ws().

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

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

    // Simple WebSocket/WebTransport handler
    app.map_ws("/ws", |mut ws: WebSocket| async move {
        // Do something when a connection established

        ws.on_msg(|msg: String| async move {
            // Do something with a message

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

This example functions similarly to the first but offers greater control over the connection.

For advanced use cases, you can split the WebSocket into separate sender and receiver components using split():

use volga::{App, ws::WebSocket};
use futures_util::{SinkExt, StreamExt};

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

    // Simple WebSocket/WebTransport handler
    app.map_ws("/ws", |ws: WebSocket| async move {
        // Split socket into sender and receiver that can be used separately
        let (mut sender, mut receiver) = ws.split();

        tokio::spawn(async move {
            let _ = sender.send("Hello from WebSockets server!".into()).await;
        });
        
        tokio::spawn(async move {
            while let Some(Ok(msg)) = receiver.next().await {
                println!("Received: {}", msg);
            }
        });
    });
    
    app.run().await
}

This example sends a single message upon connection and logs incoming messages.

For the full control, for instance, to configure a connection or specify some sub-protocols there is another one method - map_conn(), you may use it like this:

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 {
    // Here can be configured a connection and extracted something from DI or HTTP metadata
    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}")
}

Dependency Injection

You can inject any dependency from DI container by using the Dc<T> at any layer in a regular way described here.

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}")
}

You can find a complete example here.

Last Updated:
Prev
HTTPS