Actix is a Rust web framework that has been gaining popularity among developers for its performance, reliability, and scalability. One of the key features of Actix is its support for WebSockets, a protocol that enables real-time communication between client and server. In this guide, we’ll explore everything you need to know about Actix Websocket, from its installation to building a real-time chat application.
What is Actix Websocket?
Actix Websocket is a module within the Actix web framework that provides support for the WebSocket protocol. WebSocket is a communication protocol that enables bidirectional communication between the client and the server over a single TCP connection. Unlike HTTP, which is a request-response protocol, WebSocket allows real-time communication by enabling the server to push data to the client as soon as it becomes available.
Installation
Before you can start using Actix Websocket, you need to install the Actix web framework. You can do this by adding the following dependency to your Cargo.toml
file:
[dependencies]actix-web = "4.0.0-beta.8"
Once you’ve added the dependency, you can install it by running the following command:
$ cargo build
Creating a WebSocket Server
To create a WebSocket server using Actix Websocket, you need to define an endpoint that handles WebSocket connections. You can do this by defining a route that matches the WebSocket protocol:
use actix_web::{web, App, HttpRequest, HttpResponse, HttpServer, Responder};use actix_web_actors::ws;async fn index(req: HttpRequest,stream: web::Payload,) -> Result<HttpResponse, actix_web::Error> {let resp = ws::start(MyWebSocket::new(), &req, stream);resp}
struct MyWebSocket {}
impl MyWebSocket {fn new() -> Self {MyWebSocket {}}}
impl Actor for MyWebSocket {type Context = ws::WebsocketContext;
fn started(&mut self, ctx: &mut Self::Context) {println!("WebSocket connection established");}}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for MyWebSocket {fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {match msg {Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),Ok(ws::Message::Text(text)) => {println!("Received text message: {}", text);ctx.text(text);}Ok(ws::Message::Binary(bin)) => {println!("Received binary message: {:?}", bin);ctx.binary(bin);}_ => (),}}}
#[actix_web::main]async fn main() -> std::io::Result<> {HttpServer::new(|| {App::new().route("/ws/", web::get().to(index))}).bind("127.0.0.1:8080")?.run().await}
This code defines an endpoint at /ws/
that handles WebSocket connections. When a client connects to this endpoint, a new instance of the MyWebSocket
struct is created, and the started
method is called. This method is where you can perform any initialization logic, such as setting up a database connection or initializing a game state.
The StreamHandler
trait defines how your WebSocket server handles incoming messages. In this example, we’re handling three types of messages:
- Ping: We’re responding to ping messages by sending a pong message back to the client.
- Text: We’re logging the text message to the console and sending it back to the client.
- Binary: We’re logging the binary message to the console and sending it back to the client.
Connecting to a WebSocket Server
Now that you have a WebSocket server set up, you can connect to it using a WebSocket client. There are many WebSocket clients available, but one of the most popular is ws, a Node.js module.
Here’s an example of how to connect to your WebSocket server using ws
:
const WebSocket = require('ws');const ws = new WebSocket('ws://localhost:8080/ws/');
ws.on('open', function () {console.log('Connected to WebSocket server');});
ws.on('message', function (message) {console.log('Received message:', message);});
ws.on('close', function () {console.log('Disconnected from WebSocket server');});
This code creates a new WebSocket client and connects to your server at ws://localhost:8080/ws/
. When the connection is established, the open
event is emitted, and you can start sending messages to the server using the send
method.
Building a Real-Time Chat Application
Now that you know how to set up a WebSocket server and connect to it using a WebSocket client, let’s build a real-time chat application using Actix Websocket.
First, let’s define the data structures that we’ll be using to represent chat messages:
use serde::{Deserialize, Serialize};#[derive(Debug, Serialize, Deserialize)]pub struct ChatMessage {pub username: String,pub message: String,}
This code defines a ChatMessage
struct with two fields: username
and message
. We’ll be using this struct to represent chat messages that are sent between clients.
Next, let’s define the WebSocket server endpoint:
async fn chat(req: HttpRequest,stream: web::Payload,) -> Result<HttpResponse, actix_web::Error> {let resp = ws::start(ChatSession::new(), &req, stream);resp}struct ChatSession {id: usize,username: Option<String>,room: Option<String>,room_address: Option<Addr<ChatRoom>>,}
impl Actor for ChatSession {type Context = ws::WebsocketContext<Self>;
fn started(&mut self, ctx: &mut Self::Context) {println!("WebSocket connection established");let addr = ChatRoom::from_registry();self.room_address = Some(addr);}
fn stopped(&mut self, ctx: &mut Self::Context) {println!("WebSocket connection closed");if let Some(username) = &self.username {if let Some(room) = &self.room {let message = ChatMessage {username: format!("{} has left the room.", username),message: "".to_string(),};let _ = self.room_address.as_ref().unwrap().do_send(RoomMessage {room: room.to_string(),message: message,});}}}}
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for ChatSession {fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {match msg {Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),Ok(ws::Message::Text(text)) => {let message: ChatMessage = serde_json::from_str(&text).unwrap();if self.username.is_none() {self.username = Some(message.username.clone());}if self.room.is_none() {self.room = Some(message.message.clone());}let _ = self.room_address.as_ref().unwrap().do_send(RoomMessage {room: self.room.as_ref().unwrap().to_string(),message: message,});}_ => (),}}}
struct ChatRoom {rooms: HashMap<String, Vec<Addr<ChatSession>>>,}
impl ChatRoom {fn new() -> Self {ChatRoom {rooms: HashMap::new(),}}
fn join_room(&mut self, room: &str, addr: Addr<ChatSession>) {let room = self.rooms.entry(room.to_string()).or_insert_with(Vec::new);room.push(addr);}
fn leave_room(&mut self, room: &str, addr: Addr<ChatSession>) {if let Some(room) = self.rooms.get_mut(room) {room.retain(|x| *x != addr);}}
fn broadcast_message(&self, room: &str, message: ChatMessage) {if let Some(room) = self.rooms.get(room) {for addr in room {let _ = addr.do_send(message.clone());}}}}
impl Actor for ChatRoom {type Context = Context<Self>;
fn started(&mut self, _ctx: &mut Self::Context) {println!("Chat room started");}}
impl Handler<RoomMessage> for ChatRoom {type Result = ();
fn handle(&mut self, msg: RoomMessage, _ctx: &mut Self::Context) -> Self::Result {self.broadcast_message(&msg.room, msg.message);}}
struct RoomMessage {room: String,message: ChatMessage,}
impl Message for RoomMessage {type Result = ();}
This code defines a ChatSession
struct that represents a WebSocket connection. Each WebSocket connection is associated with a username and a chat room. When a client sends a message to the server, the server broadcasts the message to all clients in the same chat room.
The ChatSession
struct implements the StreamHandler
trait, which defines how the WebSocket server handles incoming messages. In this example, we’re handling two types of messages:
- Ping: We’re responding to ping messages by sending a pong message back to the client.
- Text: We’re deserializing the text message into a
ChatMessage
struct, which represents a chat message sent by a client. We’re then broadcasting the message to all clients in the same chat room.
The ChatRoom
struct represents a chat room. It maintains a list of all the clients in the room and provides methods for adding and removing clients from the room, as well as broadcasting messages to all clients in the room.
Finally, let’s define the route that handles WebSocket connections:
async fn chat_route(req: HttpRequest, stream: web::Payload) -> Result<HttpResponse, actix_web::Error> {let resp = ws::start_with_addr(ChatSession::new(), &req, stream);resp}#[actix_web::main]async fn main() -> std::io::Result<> {let chat_room = ChatRoom::new().start();HttpServer::new(move || {App::new().route("/chat/{room}/", web::get().to(chat_route)).data(chat_room.clone())}).bind("127.0.0.1:8080")?.run().await}
This code defines a route that matches the WebSocket protocol and associates it with the chat_route
function. This function creates a new instance of the ChatSession
struct and associates it with a chat room. The ChatRoom
struct is passed to the route as data.
Now you can start your server and connect to it using a WebSocket client. You can create a new chat room by connecting to the URL ws://localhost:8080/chat/{room}/
, where {room}
is the name of the chat room. Once you’re connected, you can send messages to the server using the send
method.
Conclusion
Actix Websocket is a powerful module within the Actix web framework that enables real-time communication between client and server. In this guide, we’ve explored everything you need to know about Actix Websocket, from its installation to building a real-time chat application. We hope this guide has been helpful, and we encourage you to experiment with Actix Websocket and see what you can build!
FAQ
What is Actix?
Actix is a Rust web framework that provides a fast, reliable, and scalable foundation for building web applications. It’s based on the actor model, which enables you to write highly concurrent and scalable applications.
What is WebSockets?
WebSockets is a communication protocol that enables bidirectional communication between the client and the server over a single TCP connection. Unlike HTTP, which is a request-response protocol, WebSocket allows real-time communication by enabling the server to push data to the client as soon as it becomes available.
What is the difference between HTTP and WebSockets?
HTTP is a request-response protocol that enables clients to request resources from a server and receive a response. WebSockets, on the other hand, enables bidirectional communication between the client and the server over a single TCP connection. This enables real-time communication between the client and the server, making it ideal for applications that require real-time updates, such as chat applications and online games.
What are some WebSocket clients?
There are many WebSocket clients available, including ws, a Node.js module, and Java-WebSocket, a Java library. You can also use WebSocket clients built into web browsers, such as the WebSocket
object in JavaScript.