March 15, 2022
WebSockets in Rust Actix Framework
- 36 Comments
- 37703 Views
- Categories: socket

WHAT is Web-Socket
The Web Socket API is a cutting-edge technology, providing full duplex communication between the user’s browser and a server. With this API, you can send a message to a server and receive a message over a single TCP connection. It provides easier real-time data transfer because server can send content to you without any request from you!
Advantages of Web Socket
Bidirectional
In HTTP, request is initiated by the client,
then the response is operated and returned to the client by the server. In
web-socket client and server do not need pre-defined request/response scheme –
except handshake. After handshake, either the client or server can send a
message.
Full
Duplex
In HTTP, at the same time, either client can
send a request, or the server can send a response to the client. Full duplex
communication allows that server and client can talk to each other separately.
Single TCP Connection
In general, a new TCP connection is created for an HTTP request, after getting response this connection terminated. In Web Socket, the connection is kept alive over the same TCP for the lifecycle of either the client or server.
HOW is Web-Socket
Using Rust's Actix framework, we can work with a
web socket server. Moreover, we can handle requests and response using Actix’s
actor system. At the end of the project, we are going to create an HTTP
endpoint for clients to connect to the web-socket within the topic they want to
join.
I will explain step by step how to implement in Actix.
First step:
Define a struct for Web Socket object.
pub struct WebSocketSession {
room: String,
lobby_addr: Addr<Lobby>,
hb: Instant,
id: Uuid,
}
Let me explain meaning of fields
Room: This uuid shows room id. I will implement a ‘rooms’. The rooms will be just a HashMap that maps each room Uuid to List of Socket Ids. Because each socket exists in a room.- Lobby_addr: This address shows where the lobby is. We need this id because each socket’s room exist in a lobby. We need this property because it is used to send data to lobby.
- Hb: This time shows when we receive the last heartbeat. Sometimes connection lost without any warning, so we should handle this situation. Having the actor forever is unnecessary. So, we send a heartbeat to socket every specified N second, if we do not get any response, we terminate the actor.
- Id: this is the id of socket. Assigning an id to each socket is helpful in many cases. Especially, if we want to send private message to specific socket.
Step Two: The Web-Socket Actor
So far, WebSocketSession is just a simple struct. We need to convert this struct into an actor. Let me implement the Actor trait on it.
Here is the implementation:
impl Actor for WebSocketSession {type Context = ws::WebsocketContext<Self>;fn started(&mut self, ctx: &mut Self::Context) {self.hb(ctx);let addr = ctx.address();self.lobby_addr.send(Connect {addr: addr.recipient(),lobby_id: self.room.clone(),self_id: self.id,}).into_actor(self).then(|res, _, ctx| {match res {Ok(_res) => (),_ => ctx.stop(),}fut::ready(())}).wait(ctx);}fn stopping(&mut self, _: &mut Self::Context) -> Running {self.lobby_addr.do_send(Disconnect { id: self.id, room_id: self.room.clone() });Running::Stop}}
Since we are creating WebSocket actor we need a WebSocket context. So context is declared as ws::WebsocketContext. This context allowed us to do WebSocket works. To create and close the Actor we should have started and stopping methods.
Started function is called when the actor starts
up. In started function, we run the heartbeat function first. We will see this
function in a bit, but basically it will automatically close the socket if
the heartbeat is not echo’d.
Then we take the addres of the lobby because we send a Connect message to that lobby. This message also includes id and address of the socket. I will show how lobby handle this connect message. If anything goes in wrong way, we just stop the whole Actor with ctx.stop.
In stopping function, we send a Disconnect
message to the lobby. If you noticed, do_send is used here. This allows us to
send message synchronously. We do not care if message has been sent or read. As
you seen in started method, send needs to be awaited.
WebSocketSession is now an Actor!
Here’s the helper heartbeat method
impl WebSocketSession {fn hb(&self, ctx: &mut ws::WebsocketContext<Self>) {ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {println!("Disconnecting failed heartbeat");act.lobby_addr.do_send(Disconnect { id: act.id, room_id: act.room.clone() });ctx.stop();return;}ctx.ping(b"hi");});}}
This method pings the client and wait for a response on a specified interval. If it does not get response, send a Disconnect message to the lobby. Then lobby clears this session and socket died.
How to handle WS messages
We need to implement the StreamHandler trait
that will allow us to process an events stream coming into the actor. This
pattern matches on all possible WebSocket events.
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for WebSocketSession {fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {match msg {Ok(ws::Message::Ping(msg)) => {self.hb = Instant::now();ctx.pong(&msg);}Ok(ws::Message::Pong(_)) => {self.hb = Instant::now();}Ok(ws::Message::Binary(bin)) => ctx.binary(bin),Ok(ws::Message::Close(reason)) => {ctx.close(reason);ctx.stop();}Ok(ws::Message::Continuation(_)) => {ctx.stop();}Ok(ws::Message::Nop) => (),Ok(Text(s)) => self.lobby_addr.do_send(BroadcastMessage {id: self.id,msg: serde_json::Value::String(s),room_id: self.room.clone()}),Err(e) => panic!("{}", e),}}}
The ping and pong events are basic sending of data to validate the connection is still alive. Since we know they are alive, reset the heartbeat to the current timestamp. If it is a close request, just close and stop the context. On a text message, send it to the lobby. The lobby will figure out to where it needs to go.
Responding to text messages
impl Handler<WsMessage> for WebSocketSession {type Result = ();fn handle(&mut self, msg: WsMessage, ctx: &mut Self::Context) {ctx.text(msg.0);}}
If the server puts a WsMessage into our mailbox, we just pass the message String to the actor context as a text message. We forward the messages from the server to the client in this way.
This is all about WebSocketSession for this project.
Step Three: Defining Messages for our Mailboxes
Here is the message structs we used in this project
use actix::prelude::{Message, Recipient};use uuid::Uuid;use serde_json::Value;use serde::{Deserialize, Serialize};#[derive(Message)]#[rtype(result = "()")]pub struct WsMessage(pub String);#[derive(Message)]#[rtype(result = "()")]pub struct Connect {pub addr: Recipient<WsMessage>,pub lobby_id: String,pub self_id: Uuid,}#[derive(Message)]#[rtype(result = "()")]pub struct Disconnect {pub id: Uuid,pub room_id: String,}#[derive(Message, Deserialize, Serialize, Clone)]#[rtype(result = "()")]pub struct BroadcastMessage {pub id: Uuid,pub msg: Value,pub room_id: String}impl BroadcastMessage {pub fn new(id: Uuid, data: Value, r_id: String) -> Self {Self {id,msg :data,room_id: r_id}}}
It is important to describe return type here.
Because it must be the same as the type that get returned after the message is
handled.
The msg field is one of Value from serde_json. So any type that can be formed into a Value type.
Step Four: Defining the Lobby
We need to define Lobby struct. Here is the plain simple Lobby struct.
type Socket = Recipient<WsMessage>;pub struct Lobby {sessions: HashMap<Uuid, Socket>, //self id to selfrooms: HashMap<String, HashSet<Uuid>>, //room id to list of users id}
The socket here is a recipient of WsMessage. So,
when a client connects, we can receive the Recipient< WsMessage
> from the HTTP request and store it in the Server.
Lobby will store the the current sessions using
an Uuid as a key. It will also store the chat rooms using a room Uuid as a key.
We will implement a default method for the server.
impl Default for Lobby {fn default() -> Lobby {Lobby {sessions: HashMap::new(),rooms: HashMap::new(),}}}
And here is the helper that sends a message to a specified client actor.
impl Lobby {fn send_message(&self, message: &str, id_to: &Uuid) {if let Some(socket_recipient) = self.sessions.get(id_to) {let _ = socket_recipient.do_send(WsMessage(message.to_owned()));} else {println!("attempting to send message but couldn't find user id.");}}}
This method takes a string message and an id, if the id is existing send that message to a client with that id. If it does not exist just give an error or print something says that given id does not exist.
Making the lobby an actor
We need to make our lobby an actor because
Server needs to receive messages as an Actor. Now we can pull it in to our
route handlers and send messages to it. Actix will send these messages
asynchronously.
impl Actor for Lobby {type Context = Context<Self>;}
If you noticed, we do not have to look out any
lifecycle of the Lobby. The only launch when the app starts and remove it when
the app closes.
Handling Messages
Our server will get 3 types of messages: Connects, Disconnects and WsMessage from the client actor. All of them come from the WsConn lifecycle methods from the actor trait.
impl Handler<Disconnect> for Lobby {type Result = ();fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {if self.sessions.remove(&msg.id).is_some() {self.rooms.get(&msg.room_id).unwrap().iter().filter(|conn_id| *conn_id.to_owned() != msg.id).for_each(|user_id| self.send_message(&format!("{} disconnected.", &msg.id), user_id));if let Some(lobby) = self.rooms.get_mut(&msg.room_id) {if lobby.len() > 1 {lobby.remove(&msg.id);} else {//only one in the lobby, remove it entirelyself.rooms.remove(&msg.room_id);}}}}}
Here
we are responding Disconnect message in a two different way:
1.remove a single client from a room – remove
its identifier. You can inform other clients. send everyone else X
disconnected!
2.If that client was the last one in the room,
remove the room completely to prevent blocked HashMap
impl Handler<Connect> for Lobby {type Result = ();fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {self.rooms.entry(msg.lobby_id.clone()).or_insert_with(HashSet::new).insert(msg.self_id);self.rooms.get(&msg.lobby_id).unwrap().iter().filter(|conn_id| *conn_id.to_owned() != msg.self_id).for_each(|conn_id| self.send_message(&format!("{} just joined!", msg.self_id), conn_id));self.sessions.insert(msg.self_id,msg.addr,);self.send_message(&format!("your id is {}", msg.self_id), &msg.self_id);}}
We are responding Connect message in this way:
If we cannot found room id,create
new one then add the id to it. Otherwise, inform everyone in the room that new
client just joined then add new socket id to sessions.
impl Handler<BroadcastMessage> for Lobby {type Result = ();fn handle(&mut self, msg: BroadcastMessage, _ctx: &mut Context<Self>) -> Self::Result {if let Some(_socket_recipient) = self.sessions.get(&msg.id) {self.rooms.get(&msg.room_id).unwrap().iter().for_each(|_client| self.send_message(&to_string(&msg).unwrap(), _client));} else {println!("attempting to send message but couldn't find admin id.");}}}
Finally, the lobby listen BroadcastMessage. Clients can send messages to lobby for the lobby to forward to clients.
Final Step: Setting up the Route / Running the Server
I define a route that just has a topic name as a path param. Then, I create a new WebSocketSession with a refrence to the Lobby address. Finally, I upgrade the request to a WebSocket request then I have an open persistant connection.
#[get("/{topic_name}")]pub async fn start_connection(req: HttpRequest,stream: Payload,topic_name: web::Path<String>,srv: Data<Addr<Lobby>>,) -> Result<HttpResponse, Error> {println!("client");let topic_name = topic_name.into_inner();let ws = WebSocketSession::new(topic_name,srv.get_ref().clone(),);let resp = ws::start(ws, &req, stream)?;Ok(resp)}
Then I define second route to broadcast some statistics. I call the web-socket server, converting the statistics into a value, then send it as a BroadcastMessage. Using this route, you can send statistics as an admin to everyone who connect to "dailyNews " room.
#[post("/dailyDashBoard")]pub async fn send_statistics(websocket_srv: Data<Addr<Lobby>>,params: Json<Vec<StatisticRecord>>,) -> Result<HttpResponse, Error> {let _msg =params.into_inner();let msg = BroadcastMessage{id:Uuid::parse_str("470bb217-ffa7-43d8-a0cc-b3d30421d1werfw").unwrap(),msg:json!(_msg) ,room_id: "dailyNews".to_string()};websocket_srv.do_send(msg);return Ok(HttpResponse::Ok().json(()));}
The last step is to register the Lobby as shared data so we can get address of the server. Here is the main class;
To test the socket client, you can use web-socket extension for chrome.
Have fun with web-sockets!
check out repo for the completed tutorial: edayardim/web-socket (github.com)
36 Comments
evafs5
Step mom wakes up to hard cock of step son he helps milf to cum quick https://craigslist-gay-porn.tiktok-pornhub.com/?miriam-breanna ah me porn gallery indian porn powered by phpbb hot sexy chubby plump porn videos real amature free porn perteen porn tube
eulahe9
Abby tries bdsm for the first time on dating nofilter e online https://threesome-porn-malik.celebrityamateur.com/?kaylie-berenice mature saduce young couple porn drawn together lesbo porn porn dino free porn no adds free sharing home made porn clips
ryandd4
What to know about joe gow fired uw la crosse chancellor https://pornxxxfreepornvideos.tubered69.com/?toni-ayana hardcore porn scream porn helpless forced sex teyon black porn star sweet ass porn real free cheating woman porn
Igorrwv
Ukraine
kelleymy60
Granny fiesta free granny sex movies and mature granny https://freien-bielefeld.topanasex.com/?delaney-zaria man boat women porn preggo porn sites stripper fucking porn amature moms porn free young sluts porn movies
genevieveke5
Taboo hd videos forbidden sex dark fetish action free taboo porn https://boston-rafaella.miaxxx.com/?amina-kaylynn sexiest porn teen free old man young man porn porn niggers revenge onine porn porn not flash player
winifredna3
Teens find stranger masturbating in bedroom abc7 news https://mons-rockford.fetlifeblog.com/?jordan-miya puplic domain porn free porn movies no register animal sex fun porn lanterns porn free porn fully clothed
juliony18
Chris metzen wowpedia your wiki guide to the world of warcraft https://statebankofindialogin.alypics.com/?johana-bailey porn manager 2 porn viov porn eliza dushku porn fake slim porn free hairypussy porn
kyleyq4
Random video chat with strangers cam to cam on strangercam https://asiangay69-pokemonyesixxx.amandahot.com/?payton-valentina bbw cartoon porn videos audre van zee porn free galleries thumbs porn drawings you porn fhm free long porn videos online
tommieiv69
Best porn search engines top 50 porn aggregators in 2023 https://tumblrleathermistress-funnypoemsfor5thgraders.hotblognetwork.com/?madisyn-baylee porn star babalu porn password sharing crossdresser porn iphone mother seducing daughters riend porn tracy ann porn star vancovuer
esmeraldazh8
Scandal Gallery https://socks-iop.topxxx69.com/?allyson-kathryn stephanie lazy town porn pictures porn agencies hiring hardcore porn milf doggy style wet porn sites mobile disney gone wild porn
emmahe69
11 high cholesterol foods which to eat which to avoid healthline https://son-brunette-porn.allproblog.com/?tess-quinn free porn real mouthfulls of cum porn online free full videos iphone free european porn video female porn star wilder very youngest teens porn
goldierj9
Amanda holden facts bgt star s age huxband children and https://los-angeles-temple.allproblog.com/?aleah-tatyana 2007 porn free double penitration porn videos free porn moms hidden camers brandi m porn star molly and her mother porn
beckyek5
Baby weiner size january 2013 babies forums what to expect https://maloletek.miyuhot.com/?aubree-yolanda real hot mature porn cruise porn star virtual porn for mac bodango porn heterosexual couples porn clips
sherrysu4
Newest hardcore cartoon sex videos cartoon porno xxx https://newbollywoodfullmovie.tiktokpornstar.com/?kaley-micaela johnny bravo hot porn new porn directory taylor swift porn pic amatuer porn public daughter father intimacy porn
michaelym7
The real problem with porn it s bad for sex psychology today https://labi.titsamateur.com/?arlene-elissa porn unblocked by parental controls sites porn gay xx electric porn video alina puscau porn exceptional milf porn videos
vanessavg16
25 online jobs for teens with little to no experience required https://fullnaturaltits-hotphotoshootkatrina.danexxx.com/?tori-madyson anal porn trial lesbian porn film porn hub safe site cartoon babes porn bodybuilder porn anal
leannpq11
Treasure island media tv bareback videos gay porn videos gay porn https://cervantes.hoterika.com/?jazlyn-aubrey under spell porn pics habbo hotel porn shemale big cock porn pics college girl facial porn russian porn forum bbs
selmagh9
Free lesbian girl on girl 4k hd porn videos lesbianporn4k https://passau-priestess.fetlifeblog.com/?amy-edith absolutely free porn websites young prteen porn vids free android porn videos pirate tube porn free xxx porn for mobile phones
sandracc18
Every Day Brings New Thrills: Check Out Today's Video Additions https://bofetada.a4ktube.com/?elle-kaley small large porn tiffany shepherd porn pics david roth porn totally free porn websites french men gay porn
sophielk16
Longest animal porn videos and full length zoo sex films https://madeline-download-bokep-india.tubered69.com/?kaylin-kendal fat kid porn free legal sleeping porn porn eldery women sex my rasor broke porn ginny porn
lilliancy8
20 movie download sites free legal streaming in 2022 https/:/freepotnpictures.xblognetwork.com/?melissa-eve free live porn channels youngesg hardcre porn inhumane porn site free video porn instant bedfordshire porn
wandagg7
Who is owen gray and why are people obsessed with him https://pimmel-androidsexybackxmengirlfriend.lexixxx.com/?marcella-belen comic porn windwaker beated up women porn tube sex freaks 1979 porn movie hot french porn sites free uk home made porn
beulahua11
Top 10 skinny onlyfans best skinny onlyfans models la https://candice-michelle-lesbian.tiktok-pornhub.com/?beatriz-jaden family guy un aired porn scene free porn cop mondo labia porn teen stepdaughter porn trailers yoy tube porn
johnsu2
My stunning wife wants to fuck a real male pornstar https://painties-student.instakink.com/?joyce-rachel free anime shemale porn vids savage fury porn movie marge simpsons porn porn pics free asian shemale porn lacy asian porn star glasses
rochellezl18
Getting her pregnant to impregnate her w a quick creampie https://lacroix-magdeburg.hotnatalia.com/?kallie-aniya christy parks porn movies german mom porn tube hellen matheus porn celeb porn gallerie family porn search
gregxd8
Bdsm ebony porn videos dark skinned beauties enjoy pain https://androidsexysongforgirlsqueen-kobe.alexysexy.com/?noemi-molly free sara jay porn little small dick cock porn prencess peach porn free access to cartoon porn free porn wmv clip
jewellgq7
All porn tubes free sex videos everything in one place meta porn https://sanfranciscoswingerbars.hoterika.com/?dania-diane rare porn tube free porn cum in pussy porn fo freeno registration 100 free gay teen porn porn clips in aladdin
lynnjw3
Pornhub s parentpany admits to profiting from sex trafficking https://womentramplingmen.lexixxx.com/?marlee-denise dargon ball z porn watch porn romance porn casting brutal porn orgasums redtube mexico chicks porn
zacharyzh6
All erotic scenes from the wolf of wall street xgroovy https://boulervard.instasexyblog.com/?janelle-keira sexy girls teen naked porn free homemade porn community philipino porn pictures ashley reality porn free ex girlfreind porn video
cassiehw11
Hq vintage tube free vintage retro classic tube porn movies https://hindisexvideoclip-photofunia2014online.relayblog.com/?alessandra-jenifer farm animal porn flash black home made porn tube free porn movies mother daughter forced split penis porn virain porn
aimeeuo9
Just turned 18 years young girl with very small tits https://lesbianmovielovestory-whatisthemostpopularwebpageintheworld.instasexyblog.com/?emily-yasmin promo movie monster free 15 porn hotblonde porn game free anime tentacle hentai porn hot box porn vid medieval porn threesome
carolinabs4
Two young teen girls raped by eight men at party while on https://kryvyy-rih-sticking.hotnatalia.com/?mackenzie-alma jayde porn blowjob porn movies porn violation porn film open wide free pics sex porn vids
stellamg4
Erotic paintings and tapestries downloads loverslab https://sucess.energysexy.com/?meredith-daniela sons fucking moms porn my home grown porn phonesex vidoe porn rock rose porn free americn porn movies
tracynw60
Categories xxx movies tube free porn movies at ixxx https://handling-gangbang-porn.hoterika.com/?stephany-morgan porn fetish amateur home movies hot sexy ass porn granny chubby porn anal sex porn free ebony orgy free porn movies
aileenuu9
Cyber security software and anti malware malwarebytes https://jenniferlawrenceoscarwinningmovie.danexxx.com/?karlie-janie sexy sakura haruno cosplay porn porn site with no viruses big titties asian porn site free porn babers galleries ree hairy pussy porn