Web Socket #
WebSocket adalah protokol komunikasi yang menyediakan saluran komunikasi dua arah penuh antara klien (seperti browser) dan server melalui koneksi TCP tunggal. Tidak seperti HTTP yang bersifat stateless dan bekerja dalam mode request-response, WebSocket memungkinkan data untuk ditransfer bolak-balik secara terus-menerus, membuatnya sangat cocok untuk aplikasi real-time seperti obrolan, game, atau streaming data.
Di Rust, Anda dapat membangun server dan klien WebSocket menggunakan beberapa crate (pustaka) seperti tokio-tungstenite
, warp
, dan actix-web
. Berikut adalah penjelasan lengkap mengenai cara membangun WebSocket di Rust, meliputi konsep dasar hingga penggunaan framework populer.
Pengantar WebSocket #
WebSocket dimulai dengan request handshake HTTP yang kemudian diupgrade menjadi protokol WebSocket. Setelah handshake selesai, server dan klien dapat mengirim pesan bolak-balik tanpa perlu membuka koneksi baru.
Handshake WebSocket #
Proses handshake WebSocket dimulai ketika klien mengirimkan request HTTP dengan header Upgrade
dan Connection
yang mengindikasikan keinginan untuk meng-upgrade koneksi menjadi WebSocket:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Server kemudian merespon dengan header Upgrade
dan Connection
jika mendukung WebSocket:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Setelah handshake selesai, kedua belah pihak dapat mulai mengirimkan dan menerima pesan melalui koneksi WebSocket.
Membuat WebSocket Server dengan tokio-tungstenite
#
tokio-tungstenite
adalah crate yang menyediakan implementasi WebSocket asinkron di atas Tokio, runtime asynchronous yang populer di Rust.
Instalasi Dependensi #
Pertama, tambahkan dependensi berikut ke dalam file Cargo.toml
:
[dependencies]
tokio = { version = "1", features = ["full"] }
tokio-tungstenite = "0.15"
Membuat WebSocket Server Sederhana #
Berikut adalah contoh dasar bagaimana membangun WebSocket server menggunakan tokio-tungstenite
:
use futures_util::{SinkExt, StreamExt};
use tokio::net::TcpListener;
use tokio_tungstenite::tungstenite::protocol::Message;
use tokio_tungstenite::accept_async;
#[tokio::main]
async fn main() {
let addr = "127.0.0.1:8080";
let listener = TcpListener::bind(&addr).await.expect("Gagal mengikat ke alamat");
println!("Server WebSocket berjalan di ws://{}", addr);
while let Ok((stream, _)) = listener.accept().await {
tokio::spawn(async move {
let ws_stream = accept_async(stream)
.await
.expect("Gagal menerima koneksi WebSocket");
println!("Koneksi WebSocket diterima");
let (mut ws_sender, mut ws_receiver) = ws_stream.split();
while let Some(Ok(msg)) = ws_receiver.next().await {
if msg.is_text() || msg.is_binary() {
ws_sender.send(msg).await.expect("Gagal mengirim pesan");
}
}
});
}
}
Penjelasan:
TcpListener::bind(&addr)
: Mengikat listener TCP ke alamat dan port yang ditentukan.accept_async(stream)
: Menerima koneksi TCP dan meng-upgrade menjadi WebSocket.ws_stream.split()
: Membagi WebSocket stream menjadi dua bagian, satu untuk menerima (ws_receiver
) dan satu untuk mengirim (ws_sender
).ws_receiver.next().await
: Menerima pesan dari klien secara asinkron.ws_sender.send(msg).await
: Mengirim pesan kembali ke klien (echo server).
WebSocket dengan warp
#
warp
adalah framework web yang dibangun di atas tokio
dan mendukung WebSocket secara bawaan. Warp menyediakan API yang kuat dan intuitif untuk menangani rute dan WebSocket.
Instalasi Dependensi #
Tambahkan warp
ke dalam Cargo.toml
:
[dependencies]
tokio = { version = "1", features = ["full"] }
warp = "0.3"
Membuat WebSocket Server dengan warp
#
Berikut adalah contoh implementasi WebSocket server menggunakan warp
:
use warp::Filter;
#[tokio::main]
async fn main() {
let ws_route = warp::path("ws")
.and(warp::ws())
.map(|ws: warp::ws::Ws| {
ws.on_upgrade(|websocket| async {
let (mut ws_tx, mut ws_rx) = websocket.split();
while let Some(result) = ws_rx.next().await {
if let Ok(msg) = result {
if ws_tx.send(msg).await.is_err() {
break;
}
}
}
})
});
println!("Server WebSocket berjalan di ws://127.0.0.1:3030/ws");
warp::serve(ws_route).run(([127, 0, 0, 1], 3030)).await;
}
Penjelasan:
warp::path("ws").and(warp::ws())
: Mendefinisikan rute/ws
yang mengharuskan upgrade ke WebSocket.ws.on_upgrade()
: Mengelola koneksi WebSocket setelah upgrade berhasil.websocket.split()
: Memisahkan WebSocket menjadi sender dan receiver.ws_tx.send(msg)
: Mengirim kembali pesan yang diterima (echo).
WebSocket dengan actix-web
#
actix-web
adalah framework web berbasis aktor di Rust yang juga mendukung WebSocket. actix-web
cocok untuk aplikasi yang membutuhkan pengelolaan aktor dan concurrency.
Instalasi Dependensi #
Tambahkan actix-web
dan actix
ke dalam Cargo.toml
:
[dependencies]
actix-web = "4"
actix = "0.13"
Membuat WebSocket Server dengan actix-web
#
Contoh berikut menunjukkan cara membuat WebSocket server dengan actix-web
:
use actix::{Actor, StreamHandler};
use actix_web::{web, App, HttpRequest, HttpServer, Responder};
use actix_web_actors::ws;
struct WebSocketSession;
impl Actor for WebSocketSession {
type Context = ws::WebsocketContext<Self>;
}
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)) => ctx.pong(&msg),
Ok(ws::Message::Text(text)) => ctx.text(text),
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
_ => (),
}
}
}
async fn ws_index(req: HttpRequest, stream: web::Payload) -> impl Responder {
ws::start(WebSocketSession {}, &req, stream)
}
#[actix_web::main]
async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.route("/ws/", web::get().to(ws_index))
})
.bind("127.0.0.1:8080")?
.run()
.await
}
Penjelasan:
WebSocketSession
: Struct yang mengimplementasikanActor
danStreamHandler
untuk mengelola WebSocket connection.ws::WebsocketContext<Self>
: Context yang digunakan untuk berinteraksi dengan WebSocket.StreamHandler::handle
: Mengelola pesan yang diterima dari WebSocket, sepertiPing
,Text
, danBinary
.ws::start
: Memulai sesi WebSocket ketika rute/ws/
diakses.
Mengelola State dan Broadcast #
Mengelola state dan broadcast pesan ke beberapa klien adalah kasus penggunaan umum untuk WebSocket, seperti dalam aplikasi obrolan.
State dengan WebSocket #
Anda dapat menggunakan Arc<Mutex<T>>
untuk berbagi state yang dapat diubah di antara beberapa koneksi WebSocket.
Contoh Mengelola State di tokio-tungstenite
:
use std::sync::{Arc, Mutex};
use futures_util::{SinkExt, StreamExt};
use tokio::net::TcpListener;
use tokio_tungstenite::{accept_async, tungstenite::protocol::Message};
type SharedState = Arc<Mutex<Vec<tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>>>>;
#[tokio::main]
async fn main() {
let state: SharedState = Arc::new(Mutex::new(Vec::new()));
let addr = "127.0.0.1:8080";
let listener = TcpListener::bind(&addr).await.expect("Gagal mengikat ke alamat");
println!("Server WebSocket berjalan di ws://{}", addr);
while let Ok((stream, _)) = listener.accept().await {
let state = Arc::clone(&state);
tokio::spawn(async move {
let ws_stream = accept_async(stream)
.await
.expect("Gagal menerima koneksi WebSocket");
let (mut ws_sender, mut ws_receiver) = ws_stream.split();
state.lock().unwrap().push(ws_sender);
while let Some(Ok(msg)) = ws_receiver.next().await {
let peers = state.lock().unwrap();
for peer in peers.iter() {
let _ = peer.send(msg.clone()).await;
}
}
});
}
}
Penjelasan:
SharedState
:Arc<Mutex<Vec<tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>>>>
digunakan untuk menyimpan dan mengelola koneksi WebSocket yang aktif.- Broadcast: Setiap pesan yang diterima dari satu klien akan dikirimkan ke semua klien yang terhubung.
Mengamankan WebSocket dengan TLS #
Untuk komunikasi yang aman, Anda perlu mengamankan koneksi WebSocket dengan TLS (wss://). tokio-tungstenite
dan warp
mendukung TLS melalui native-tls
atau rustls
.
Contoh Penggunaan TLS dengan warp
:
use warp::Filter;
use tokio_rustls::TlsAcceptor;
use rustls::ServerConfig;
#[tokio::main]
async fn main() {
let ws_route = warp::path("ws")
.and(warp::ws())
.map(|ws: warp::ws::Ws| {
ws.on_upgrade(|websocket| async {
let (mut ws_tx, mut ws_rx) = websocket.split();
while let Some(result) = ws_rx.next().await {
if let Ok(msg) = result {
if ws_tx.send(msg).await.is_err() {
break;
}
}
}
})
});
let certs = load_certs("cert.pem");
let key = load_private_key("key.pem");
let config = ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(certs, key)
.expect("TLS setup error");
let tls_acceptor = TlsAcceptor::from(Arc::new(config));
println!("Server WebSocket dengan TLS berjalan di wss://127.0.0.1:3030/ws");
warp::serve(ws_route)
.tls()
.cert_path("cert.pem")
.key_path("key.pem")
.run(([127, 0, 0, 1], 3030))
.await;
}
fn load_certs(path: &str) -> Vec<rustls::Certificate> {
let certfile = std::fs::File::open(path).expect("could not open certificate file");
let mut reader = std::io::BufReader::new(certfile);
rustls_pemfile::certs(&mut reader).expect("could not load certificate")
.into_iter()
.map(rustls::Certificate)
.collect()
}
fn load_private_key(path: &str) -> rustls::PrivateKey {
let keyfile = std::fs::File::open(path).expect("could not open private key file");
let mut reader = std::io::BufReader::new(keyfile);
let keys = rustls_pemfile::pkcs8_private_keys(&mut reader).expect("could not load private key");
rustls::PrivateKey(keys[0].clone())
}
Penjelasan:
TlsAcceptor
: Digunakan untuk menerima koneksi TLS.ServerConfig
: Digunakan untuk mengkonfigurasi sertifikat dan kunci TLS.cert.pem
dankey.pem
: File sertifikat dan kunci privat yang digunakan untuk TLS.
Kesimpulan #
WebSocket di Rust memungkinkan Anda untuk membangun aplikasi real-time yang aman dan efisien. Dengan menggunakan crate seperti tokio-tungstenite
, warp
, dan actix-web
, Anda dapat dengan mudah membuat server WebSocket yang mendukung komunikasi dua arah, state management, dan koneksi aman melalui TLS. Rust memberikan keamanan memori dan performa tinggi, yang membuatnya sangat cocok untuk membangun aplikasi WebSocket yang dapat diandalkan dan scalable.