DEV Community

Rogério Araújo
Rogério Araújo

Posted on

Real-time Communication with WebSockets using deboa-extras

WebSockets provide a powerful way to enable real-time, bidirectional communication between clients and servers. In this article, we'll explore how to implement WebSocket clients using the deboa-extras crate, a lightweight and flexible HTTP client library for Rust.

Introduction to deboa-extras WebSocket Support

The deboa-extras crate extends the core deboa HTTP client with WebSocket capabilities, making it easy to establish WebSocket connections and exchange messages in real-time. It's built on top of the hyper library and provides an ergonomic API for WebSocket communication.

Setting Up Your Project

First, add the necessary dependencies to your Cargo.toml:

[dependencies]
deboa = "0.1.0"
deboa-extras = { version = "0.1.0", features = ["ws"] }
tokio = { version = "1.0", features = ["full"] }
Enter fullscreen mode Exit fullscreen mode

Establishing a WebSocket Connection

To create a WebSocket connection, you'll use the WebsocketRequestBuilder trait:

use deboa::{Deboa, Result, request::DeboaRequestBuilder};
use deboa_extras::ws::{
    io::socket::DeboaWebSocket,
    protocol::Message,
    request::WebsocketRequestBuilder,
    response::IntoWebSocket,
};

#[tokio::main]
async fn main() -> Result<()> {
    let mut client = Deboa::new();

    // Create a WebSocket connection
    let mut websocket = DeboaRequestBuilder::websocket("wss://echo.websocket.org")?
        .send_with(&mut client)
        .await?
        .into_websocket()
        .await?;

    // Now you can send and receive messages
    websocket.send_text("Hello, WebSocket!").await?;

    if let Some(message) = websocket.read_message().await? {
        match message {
            Message::Text(text) => println!("Received: {}", text),
            Message::Binary(data) => println!("Received binary data: {:?}", data),
            Message::Close(code, reason) => println!("Connection closed: {} - {}", code, reason),
            _ => {} // Handle other message types if needed
        }
    }

    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Sending and Receiving Messages

The DeboaWebSocket trait provides several methods for different types of messages:

// Send a text message
websocket.send_text("Hello, WebSocket!").await?;

// Send binary data
websocket.send_binary(&[0x01, 0x02, 0x03]).await?;

// Send a ping
websocket.send_ping(b"ping").await?;

// Close the connection
websocket.send_close(1000, "Normal closure").await?;
Enter fullscreen mode Exit fullscreen mode

Handling Incoming Messages

To handle incoming messages, you'll typically use a loop:

loop {
    match websocket.read_message().await? {
        Some(Message::Text(text)) => {
            println!("Received text: {}", text);
            // Echo back the message
            websocket.send_text(format!("You said: {}", text)).await?;
        }
        Some(Message::Binary(data)) => {
            println!("Received binary data: {:?}", data);
        }
        Some(Message::Ping(data)) => {
            println!("Received ping, sending pong");
            websocket.send_pong(data).await?;
        }
        Some(Message::Close(code, reason)) => {
            println!("Connection closed: {} - {}", code, reason);
            break;
        }
        None => {
            println!("Connection closed by server");
            break;
        }
        _ => {} // Handle other message types
    }
}
Enter fullscreen mode Exit fullscreen mode

Error Handling

The WebSocket implementation in deboa-extras provides detailed error handling:

match websocket.read_message().await {
    Ok(Some(message)) => {
        // Handle message
    }
    Err(e) => match e {
        DeboaExtrasError::WebSocket(WebSocketError::SendMessage { message }) => {
            eprintln!("Failed to send message: {}", message);
        }
        DeboaExtrasError::WebSocket(WebSocketError::ReceiveMessage { message }) => {
            eprintln!("Failed to receive message: {}", message);
        }
        _ => eprintln!("Unexpected error: {:?}", e),
    },
    _ => {} // Handle other cases
}
Enter fullscreen mode Exit fullscreen mode

Building a Simple Chat Application

Let's create a simple chat client that connects to a WebSocket server and echoes messages:

use deboa::{Deboa, Result, request::DeboaRequestBuilder};
use deboa_extras::ws::{
    io::socket::DeboaWebSocket,
    protocol::Message,
    request::WebsocketRequestBuilder,
    response::IntoWebSocket,
};
use tokio::io::{self, AsyncBufReadExt};

#[tokio::main]
async fn main() -> Result<()> {
    println!("Connecting to WebSocket server...");

    let mut client = Deboa::new();
    let mut websocket = DeboaRequestBuilder::websocket("wss://echo.websocket.org")?
        .send_with(&mut client)
        .await?
        .into_websocket()
        .await?;

    println!("Connected! Type a message and press Enter to send. Type 'exit' to quit.");

    let stdin = io::stdin();
    let mut reader = io::BufReader::new(stdin).lines();

    loop {
        tokio::select! {
            // Read from stdin
            Ok(Some(line)) = reader.next_line() => {
                if line.trim() == "exit" {
                    websocket.send_close(1000, "User requested close").await?;
                    break;
                }
                if !line.trim().is_empty() {
                    websocket.send_text(&line).await?;
                }
            }

            // Read from WebSocket
            result = websocket.read_message() => {
                match result {
                    Ok(Some(Message::Text(text))) => {
                        println!("Server: {}", text);
                    }
                    Ok(Some(Message::Close(code, reason))) => {
                        println!("Server closed connection: {} - {}", code, reason);
                        break;
                    }
                    Ok(None) => {
                        println!("Connection closed by server");
                        break;
                    }
                    Err(e) => {
                        eprintln!("Error reading message: {}", e);
                        break;
                    }
                    _ => {} // Ignore other message types
                }
            }
        }
    }

    println!("Disconnected");
    Ok(())
}
Enter fullscreen mode Exit fullscreen mode

Best Practices

  1. Error Handling: Always handle potential errors when sending and receiving messages.
  2. Connection Management: Implement proper connection handling with reconnection logic.
  3. Message Framing: Be aware of message size limits and implement fragmentation if needed.
  4. Resource Cleanup: Always close the WebSocket connection properly when done.
  5. Concurrency: Use tokio::select! to handle multiple asynchronous operations concurrently.

Conclusion

The deboa-extras crate provides a simple yet powerful way to work with WebSockets in Rust. With its intuitive API and support for different message types, you can easily build real-time applications that require bidirectional communication.

Whether you're building a chat application, a real-time dashboard, or any other application that requires real-time updates, deboa-extras has you covered with its WebSocket support.

Next Steps

  1. Explore more advanced features like message compression
  2. Implement custom serialization for complex message types
  3. Add authentication to your WebSocket connections
  4. Handle network interruptions and implement reconnection logic

Happy coding with WebSockets and deboa-extras!

Top comments (0)