# How to Implement WebSockets in Topcoat: A Complete Guide with Examples

> Implement WebSockets in Topcoat with this comprehensive guide. Learn how to leverage topcoat-router and the websocket feature for real-time communication in your applications. Get started today.

- Repository: [Tokio/topcoat](https://github.com/tokio-rs/topcoat)
- Tags: how-to-guide
- Published: 2026-07-31

---

**Topcoat provides first-class WebSocket support through the `topcoat-router` crate, gated by the `websocket` feature.**

To implement WebSockets in Topcoat, you use the `WebSocketUpgrade` extractor to handle the handshake and spawn async tasks for message exchange. The `tokio-rs/topcoat` repository implements this through the `topcoat-router` crate, where WebSocket endpoints behave like standard HTTP routes but receive a `WebSocketUpgrade` that transitions the connection after protocol negotiation. Once upgraded, the `WebSocket` type implements both `Stream` and `Sink` traits, allowing you to `recv().await` incoming frames and `send(msg).await` responses using the `Message` enum.

## Enabling WebSocket Support

WebSocket functionality lives in the `topcoat-router` crate and must be enabled via the `websocket` feature flag in your [`Cargo.toml`](https://github.com/tokio-rs/topcoat/blob/main/Cargo.toml):

```toml
[dependencies]
topcoat-router = { version = "*", features = ["websocket"] }

```

This exposes the `WebSocketUpgrade` extractor and `WebSocket` connection type under `topcoat::router::content::websocket`.

## The WebSocket Handshake Flow

Topcoat handles the WebSocket handshake through three distinct phases managed by the `WebSocketUpgrade` extractor in [`src/content/websocket/upgrade.rs`](https://github.com/tokio-rs/topcoat/blob/main/src/content/websocket/upgrade.rs).

### Request Validation

When a client sends a `GET` request with `Upgrade: websocket` headers, the `WebSocketUpgrade` extractor validates the handshake automatically. It rejects non-GET methods with a `405 Method Not Allowed` status and malformed handshakes with `400 Bad Request`, ensuring only valid WebSocket connections proceed.

### Protocol Upgrade

Inside your route handler, calling `WebSocketUpgrade::on_upgrade` initiates the handshake response while spawning an independent async task. The closure passed to `on_upgrade` receives a fully established `WebSocket` instance once the client switches protocols, allowing the route to return the HTTP response immediately without blocking.

```rust
use topcoat::{
    Result,
    router::{
        content::websocket::{Message, WebSocketUpgrade},
        response::Response,
        route,
    },
};

#[route(GET "/ws/echo")]
async fn echo(upgrade: WebSocketUpgrade) -> Result<Response> {
    upgrade.on_upgrade(|mut socket| async move {
        while let Some(Ok(msg)) = socket.recv().await {
            if matches!(msg, Message::Text(_) | Message::Binary(_)) {
                if socket.send(msg).await.is_err() {
                    break;
                }
            }
        }
    })
}

```

## Handling Messages with the WebSocket Type

The `WebSocket` type in [`src/content/websocket/socket.rs`](https://github.com/tokio-rs/topcoat/blob/main/src/content/websocket/socket.rs) implements `Stream<Item = Result<Message>>` and `Sink<Message>`, providing idiomatic async Rust interfaces for bidirectional communication.

### Message Types

The `Message` enum defined in [`src/content/websocket/message.rs`](https://github.com/tokio-rs/topcoat/blob/main/src/content/websocket/message.rs) covers all WebSocket frame types:

- **`Text(String)`** – UTF-8 encoded application data
- **`Binary(Vec<u8>)`** – Raw binary application data  
- **`Ping`** – Automatic heartbeat requests (automatically answered by the server, but still emitted for monitoring)
- **`Pong`** – Heartbeat responses
- **`Close`** – Connection termination, optionally carrying a `CloseFrame` with status code and reason

### Reading and Writing Messages

Use `recv().await` to read incoming frames and `send(msg).await` to transmit data. The connection remains open until you call `socket.close().await` or the client sends a close frame.

```rust
use futures_util::{StreamExt, SinkExt};
use topcoat::router::content::websocket::WebSocketUpgrade;

#[route(GET "/ws/chat")]
async fn chat(upgrade: WebSocketUpgrade) -> Result<Response> {
    upgrade.on_upgrade(|socket| async move {
        let (mut sender, mut receiver) = socket.split();
        
        while let Some(Ok(msg)) = receiver.next().await {
            let _ = sender.send(msg).await;
        }
    })
}

```

## Configuring Sub-Protocols and Limits

The `WebSocketUpgrade` builder pattern allows fine-grained control over the handshake via methods defined in [`src/content/websocket/upgrade.rs`](https://github.com/tokio-rs/topcoat/blob/main/src/content/websocket/upgrade.rs).

### Sub-Protocol Negotiation

Declare supported sub-protocols using `protocols()`. Topcoat selects the first match from the client's request and echoes it back, accessible via `socket.protocol()`:

```rust
#[route(GET "/ws/protocol")]
async fn protocol(upgrade: WebSocketUpgrade) -> Result<Response> {
    upgrade
        .protocols(&["json", "chat"])
        .on_upgrade(|socket| async move {
            if let Some(proto) = socket.protocol() {
                println!("Negotiated: {}", proto.to_str().unwrap());
            }
        })
}

```

### Security Limits

Protect against resource exhaustion using size guards:

- **`max_message_size(256 * 1024)`** – Rejects payloads exceeding 256 KiB
- **`max_frame_size`** – Limits individual frame sizes
- **`max_write_buffer_size`** – Prevents unbounded memory growth when clients stop reading

## Key Source Files in tokio-rs/topcoat

Understanding the implementation details helps when debugging or extending functionality:

- **[`src/content/websocket/socket.rs`](https://github.com/tokio-rs/topcoat/blob/main/src/content/websocket/socket.rs)** – Implements the `WebSocket` struct, `Stream`/`Sink` traits, and low-level frame handling
- **[`src/content/websocket/upgrade.rs`](https://github.com/tokio-rs/topcoat/blob/main/src/content/websocket/upgrade.rs)** – Defines `WebSocketUpgrade` extractor and handshake validation logic  
- **[`src/content/websocket/message.rs`](https://github.com/tokio-rs/topcoat/blob/main/src/content/websocket/message.rs)** – Contains the `Message` enum and conversions between tungstenite frames and Topcoat types
- **[`src/route.rs`](https://github.com/tokio-rs/topcoat/blob/main/src/route.rs)** – Houses the `#[route]` procedural macro used to declare WebSocket endpoints

## Summary

- **Enable** WebSocket support via the `websocket` feature in `topcoat-router`
- **Extract** the handshake using `WebSocketUpgrade` in route handlers
- **Upgrade** connections with `on_upgrade()` to spawn async message handling tasks
- **Exchange** data using `recv()` and `send()` on the `WebSocket` type, which implements `Stream`/`Sink`
- **Configure** sub-protocols and size limits using the builder pattern on `WebSocketUpgrade`
- **Reference** the source in `src/content/websocket/` for implementation details of the socket and message types

## Frequently Asked Questions

### How do I enable WebSocket support in Topcoat?

Add the `websocket` feature to your `topcoat-router` dependency in [`Cargo.toml`](https://github.com/tokio-rs/topcoat/blob/main/Cargo.toml). This gates the `WebSocketUpgrade` extractor and related types to keep compile times minimal when WebSocket functionality is not needed.

### What is the difference between WebSocketUpgrade and WebSocket?

`WebSocketUpgrade` is the extractor that handles the initial HTTP handshake and validation. It exists only during the request phase. `WebSocket` represents the established bidirectional stream after the handshake completes, providing the `recv()` and `send()` methods for message exchange in the spawned async task.

### How does Topcoat handle incoming Ping frames?

According to the implementation in [`src/content/websocket/socket.rs`](https://github.com/tokio-rs/topcoat/blob/main/src/content/websocket/socket.rs), incoming `Ping` frames are automatically answered with `Pong` frames at the protocol level. However, they are still emitted as `Message::Ping` items through the `Stream` implementation, allowing your application to monitor or log heartbeat activity if desired.

### Can I use middleware with WebSocket routes in Topcoat?

Yes. Because `WebSocketUpgrade` functions as a standard extractor, you can compose it with session extractors, cookie parsers, or authentication middleware before calling `on_upgrade()`. The middleware runs during the HTTP phase, allowing you to reject unauthorized requests before the WebSocket handshake occurs.