# Tungstenite: a bare-bones WebSocket implementation for Rust

> Tungstenite is a synchronous, stream-based RFC6455 WebSocket library for Rust that leaves framing, handshakes and TLS feature selection to you. It is the right base layer when you need control over the socket, and the wrong one when you want a batteries-included async server.

**snapview/tungstenite-rs** — Lightweight stream-based WebSocket implementation for Rust.

- Repository: https://github.com/snapview/tungstenite-rs
- Stars: 2,396 · Forks: 296
- Language: Rust
- License: Apache-2.0
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/snapview-tungstenite-rs

## What problem Tungstenite solves, and for whom

Rust has no WebSocket in its standard library. If you want a server or client that speaks RFC6455, you either pull in a framework that hides the protocol or you implement framing, masking and the opening handshake yourself. Tungstenite occupies the middle ground: it implements the protocol and exposes it over anything that behaves like a stream, so the socket, the event loop and the threading model stay under your control. The README describes the crate as "more like a barebone to build reliable modern networking applications using WebSockets." That phrasing is a positioning statement, not a limitation of quality. The audience is people writing infrastructure: an embedded WebSocket endpoint inside a service that already owns its accept loop, a test harness that needs to drive a WebSocket by hand, or an author building a runtime-specific wrapper. The README explicitly redirects anyone who wants non-blocking sockets and "full-duplex" communication to tokio-tungstenite. Read that as the project telling you where its own boundary lies. If your application is an async service with many concurrent connections, Tungstenite is a component, not the answer.

## How the stream abstraction and RFC6455 framing fit together

The crate is built around a generic stream parameter. TcpStream is the obvious one, but the README states the library "allows for both synchronous (like TcpStream) and asynchronous usage and is easy to integrate into any third-party event loops including MIO." The mechanism is that the WebSocket layer reads and writes through the stream you hand it, so the transport is your choice and the protocol logic is the crate's. The README also says the API design "abstracts away all the internals of the WebSocket protocol but still makes them accessible for those who wants full control over the network." In practice that means the message-level API (read, send) is what you use most of the time, while the framing is reachable when you need it. The accept path performs the HTTP upgrade handshake, which is why the default feature set is called handshake and pulls in data-encoding, http, httparse and sha1. Turn that feature off and you are working with an already-upgraded connection. TLS sits on top as a set of mutually exclusive features rather than a default: native-tls, native-tls-vendored, rustls-tls-native-roots and rustls-tls-webpki-roots. The README warns plainly that by default no TLS feature is activated, "so make sure you use one of the TLS features, otherwise you won't be able to communicate with the TLS endpoints." That is the single most common configuration mistake with this crate, and it fails at connect time rather than at compile time.

## Building a first echo server with Tungstenite

The crate is published on crates.io under the package name tungstenite; the Cargo.toml in the repository lists version 0.30.0 and rust-version 1.85, so confirm your toolchain is at least that. The README gives a complete echo server. It binds 127.0.0.1:9001, accepts each incoming TCP connection on its own thread, upgrades it, and echoes binary and text frames back while dropping ping and pong messages:

```rust
use std::net::TcpListener;
use std::thread::spawn;
use tungstenite::accept;

fn main () {
    let server = TcpListener::bind("127.0.0.1:9001").unwrap();
    for stream in server.incoming() {
        spawn (move || {
            let mut websocket = accept(stream.unwrap()).unwrap();
            loop {
                let msg = websocket.read().unwrap();
                if msg.is_binary() || msg.is_text() {
                    websocket.send(msg).unwrap();
                }
            }
        });
    }
}
```

What you should see is a process listening on port 9001 that echoes any text or binary frame. The is_binary and is_text guard matters: without it the server would echo control frames, which is not what the protocol expects. The repository also ships examples/server.rs, examples/client.rs and examples/autobahn-server.rs, so `cargo run --example server` is the fastest way to look at a fuller version before writing your own. When you add the dependency, select a TLS feature only if you need wss://; the README states that no TLS feature is activated by default.

## No permessage-deflate, and other boundaries you will hit

The README states there is no support for permessage-deflate "at the moment, but the PRs are welcome." That is a real functional gap, not a cosmetic one. Browsers negotiate that extension by default, and a server that does not implement it will simply not compress, which costs bandwidth on text-heavy traffic. There is no way to turn it on through a feature flag. The second boundary is the threading model implied by the examples: one thread per connection, blocking reads. That is fine for a few dozen connections and wrong for tens of thousands. The README does not document a connection limit, a backpressure strategy, or a timeout policy, so you are responsible for those. Third, TLS is opt-in and the failure mode is a runtime error rather than a compile error, as noted above. Fourth, the crate implements the protocol, not the application: no routing, no rooms, no reconnect logic, no message-size policy beyond what you write. If you want those, you are looking at a framework, not at Tungstenite. The project is not archived and the last push was on 2026-07-11, which is recent, but the README itself does not publish a support window or a release cadence, so plan for the version you pin rather than for a promise.

## Tungstenite versus tokio-tungstenite: same protocol, different runtime contract

The comparison the project itself makes is the useful one. Tokio-tungstenite is the same WebSocket protocol implementation wrapped for the Tokio runtime, and the README recommends it for anyone who wants "non-blocking sockets" and "full-duplex" communication in a "modern production-ready" library. The difference is not in the wire format, it is in who owns the event loop. With Tungstenite you own it: you decide when a read blocks, which thread handles which connection, and how the socket integrates with MIO or another reactor. With tokio-tungstenite the runtime owns it, and you write async functions against a stream that yields when it has nothing to give. That trade is not free in either direction. The synchronous path is easier to reason about and easier to embed in a program that already has a blocking accept loop; the async path scales connections without a thread each. If your project already depends on Tokio, adding Tungstenite means you are also writing the adapter that tokio-tungstenite already provides. If your project has no async runtime and you do not want one, the reverse is true. The README also mentions MIO as a supported integration point, which is the middle path for a custom reactor.

## Licence, testing and what the repository actually ships

The crate is dual licensed MIT OR Apache-2.0, and the repository carries both LICENSE-MIT and LICENSE-APACHE. The README's badges show MIT and Apache-2.0 as well. That is the standard Rust ecosystem pairing, and it means downstream users can choose either set of terms; it is not legal advice, and if your organisation has a policy on dual-licensed dependencies, check it against both files rather than one. On testing, the README states the library "passes the Autobahn Test Suite for WebSockets" and is "covered by internal unit tests as well as possible." The repository layout backs that up: there is an autobahn/ directory, a tests/ directory, a fuzz/ directory, and examples/autobahn-client.rs and examples/autobahn-server.rs for driving the suite. Benches live in benches/ and are run with `cargo bench --bench \* -- --quick --noplot`, or a single set with `cargo bench --bench e2e -- --quick --noplot`. The package include list ships benches, src, examples, the licence files, README and CHANGELOG, which is a lean published artefact. Version 0.30.0 is pre-1.0, so semver allows breaking changes in minor releases; pin your dependency and read CHANGELOG.md before upgrading.

## Conclusion

Adopt Tungstenite when you are writing a synchronous WebSocket endpoint, embedding WebSocket framing into an existing event loop, or building a higher-level library such as tokio-tungstenite. Do not adopt it for a production async server that needs permessage-deflate, because the README states there is no support for that extension and points to tokio-tungstenite instead. Before committing, confirm that rust-version 1.85 matches your toolchain, that you have enabled exactly one TLS feature, and that your transport is a stream type the library can wrap.

## FAQ

### How do I use WebSockets in Rust with Tungstenite?

Add the tungstenite crate, pick a TLS feature if you need wss://, and use accept() on the server side or the client helper on the client side. The README's echo server binds a TcpListener, calls accept on each incoming stream, then loops on websocket.read() and websocket.send().

### What is WebSocket technology in the context of Tungstenite?

Tungstenite implements RFC6455, the WebSocket protocol, over a generic stream. The README describes it as a lightweight stream-based implementation that abstracts the protocol internals while keeping them accessible.

### How do I enable WebSockets with Tungstenite?

Enable the handshake feature, which is on by default and pulls in data-encoding, http, httparse and sha1 for the opening handshake. For secure endpoints you must also enable one TLS feature, because the README states no TLS feature is activated by default.

## Sources

- [Issues](https://github.com/snapview/tungstenite-rs/issues)
- [License: Apache-2.0](https://github.com/snapview/tungstenite-rs/blob/master/LICENSE)
- [README](https://github.com/snapview/tungstenite-rs/blob/master/README.md)
- [snapview/tungstenite-rs on GitHub](https://github.com/snapview/tungstenite-rs)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/snapview-tungstenite-rs
