Skip to main content
Lesson 1 of 1Advanced⏱ 15 mins

WebSocket Basics

Upgrading HTTP to WebSockets.

0% of path

Learning Objectives

  • Accept a WebSocket connection

##Real-time communication

HTTP follows a strict pattern: the client asks, the server answers. For live features like chat, notifications, dashboards and multiplayer games the server needs to push data whenever something happens. That is what WebSockets are for.

#How it works

A WebSocket starts as a normal HTTP request with an Upgrade: websocket header. If the server agrees, the connection upgrades and stays open. After that, both sides can send messages at any time, in either direction, with very little overhead.

#Setup

code_blockstoml
[dependencies]
actix-web = "4"
actix-ws = "0.3"
futures-util = "0.3"
tokio = { version = "1", features = ["rt", "macros"] }

#An echo server

code_blocksrust
use actix_web::{web, App, Error, HttpRequest, HttpResponse, HttpServer};
use actix_ws::Message;
use futures_util::StreamExt as _;

async fn ws(req: HttpRequest, body: web::Payload) -> Result<HttpResponse, Error> {
    let (response, mut session, mut stream) = actix_ws::handle(&req, body)?;

    actix_web::rt::spawn(async move {
        while let Some(Ok(msg)) = stream.next().await {
            match msg {
                Message::Text(text) => {
                    if session.text(format!("echo: {text}")).await.is_err() {
                        break;
                    }
                }
                Message::Ping(bytes) => {
                    if session.pong(&bytes).await.is_err() {
                        break;
                    }
                }
                Message::Close(reason) => {
                    let _ = session.close(reason).await;
                    break;
                }
                _ => {}
            }
        }
    });

    Ok(response)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| App::new().route("/ws", web::get().to(ws)))
        .bind(("127.0.0.1", 8080))?
        .run()
        .await
}

#Understanding the pieces

  • actix_ws::handle performs the upgrade handshake and returns the HTTP response, a session (for sending) and a stream (for receiving).
  • spawn runs the message loop in a background task so the handler can return the upgrade response immediately.
  • Messages arrive as Message::Text, Binary, Ping, Pong or Close.
  • Reply to Ping with pong to keep the connection healthy.

#Testing it

In a browser console:

terminalbash
# or use the browser:
# const ws = new WebSocket("ws://127.0.0.1:8080/ws");
# ws.onmessage = (e) => console.log(e.data);
# ws.onopen = () => ws.send("hello");

Or use a tool such as websocat ws://127.0.0.1:8080/ws.

#Broadcasting to many clients

For a chat room you keep a list of sessions in shared state and send each message to all of them, usually through a tokio::sync::broadcast channel. Each connection subscribes to the channel and forwards messages to its own client.

#Production tips

  • Authenticate during the HTTP upgrade request (a token in a header or query parameter).
  • Send periodic pings and drop idle connections.
  • Limit message size and number of connections per user.
  • Behind a reverse proxy, enable WebSocket upgrade support and long timeouts.

"What If?" Experiment

Learning by doing. Modify the code to see what happens.

Goal

Watch messages flow in both directions.

Starting Code
websocat ws://127.0.0.1:8080/ws
Suggested Modification

Type a few lines into websocat.

Expected Result

Each line is echoed back immediately.

Explanation: The connection stays open, so no new HTTP request is needed for each message.

Common Mistakes

Anticipate and avoid these frequent pitfalls when working with this concept.

!

Running the message loop inside the handler

The handler must return the upgrade response; looping there blocks it forever.

Incorrect Pattern
while let Some(msg) = stream.next().await { /* ... */ } Ok(response)
Correct Pattern
actix_web::rt::spawn(async move { while let Some(Ok(msg)) = stream.next().await { /* ... */ } }); Ok(response)

Explanation: Spawn the loop so the handler can return immediately and complete the handshake.

!

No authentication on the WebSocket

Browsers cannot set custom headers on a WebSocket, so people skip auth.

Incorrect Pattern
// anyone can connect
Correct Pattern
// validate a token from a query parameter or cookie during the upgrade request

Explanation: Check credentials in the handler before calling actix_ws::handle, and refuse the upgrade with 401 if they are missing.

Practical Exercise

Build a shouting echo

Task

Change the echo server so it replies with the received text in uppercase.

Requirements

  • Reply to text messages
  • Use to_uppercase

Need a hint?

Try solving the exercise in your local environment first. Once you have a solution, or if you get completely stuck, reveal the answer.

Real-World Application

Chat apps, trading dashboards, collaborative editors and live notifications run on persistent connections like this, often with a broadcast channel fanning each event out to thousands of sessions.

Key Takeaways

  • A WebSocket starts as an HTTP upgrade and then stays open for two-way messages.
  • actix_ws::handle returns the response, a session for sending and a stream for receiving.
  • Run the message loop in a spawned task and answer Ping with Pong.
  • Authenticate during the upgrade and limit connections and message size.

Finished reading?

Mark this lesson as done to track your progress and earn XP.