From b6f786fcf17ced524590f98e7bbc8310180edafb Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Thu, 19 Feb 2026 06:06:40 +0100 Subject: [PATCH 1/3] Simple readme --- README.md | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- logo.svg | 28 +++++++++++++++++++ 2 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 logo.svg diff --git a/README.md b/README.md index 6cd12d5..d64376b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,79 @@ -# session-rs -A lightweight async protocol for WebSocket. +

+ Session +

+

session-rs

+

A lightweight, async WebSocket protocol for Rust.

+ +## Introduction + +This library provides **type-safe WebSocket communication** with a request-response and notification system built on top of a flexible protocol. +It ensures compile-time guarantees for message structure, reduces runtime errors, and simplifies building Rust client/server applications. + +- **Dynamic Methods**: Each message includes a method enum for type safety. +- **Typed Requests & Responses**: Automatic serialization and deserialization. +- **Optional Notifications**: Send asynchronous notifications across sessions. + +## Features + +- Fully typed WebSocket sessions +- Type-safe request/response mechanism +- Optional typed notifications (Todo) +- Lightweight, minimal runtime overhead +- Async-first with Tokio support + +## Installation + +```bash +cargo add session-rs +``` + +--- + +### **Basic Example (client)** + +```rust +#[derive(Debug, Serialize, Deserialize)] +struct Data; + +impl Method for Data { + const NAME: &'static str = "data"; + type Request = String; + type Response = String; + type Error = String; +} + +let session = Session::connect("127.0.0.1:8080", "/").await?; + +session.start_receiver(); + +session + .request::("Hello from client".to_string()) + .await?; +``` + +### **Basic Example (server)** + +```rust +#[derive(Debug, Serialize, Deserialize)] +struct Data; + +impl Method for Data { + const NAME: &'static str = "data"; + type Request = String; + type Response = String; + type Error = String; +} + +let server = SessionServer::bind("127.0.0.1:8080").await?; + +loop { + let session = server.accept().await; + + session + .on::(async |_, req| { + // Response is required + Ok("Hello from server".to_string()) + }) + .await; +} +``` diff --git a/logo.svg b/logo.svg new file mode 100644 index 0000000..7f42bb9 --- /dev/null +++ b/logo.svg @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + From c3dd874a87f03082860b15269364950bb259c25e Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Thu, 19 Feb 2026 12:31:20 +0100 Subject: [PATCH 2/3] protocol --- README.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/README.md b/README.md index d64376b..6ec622d 100644 --- a/README.md +++ b/README.md @@ -77,3 +77,29 @@ loop { .await; } ``` + +## Protocol + +#### Request + +The request `id` is separated from the peer, and will increment only on it's requests. + +```json +{ "type": "request", "id": 1, "method": "data", "data": "Hello from client" } +``` + +#### Response + +The response `id` **must** remain the same as the request. + +```json +{ "type": "response", "id": 1, "result": "Hello from server" } +``` + +#### Notifications + +A notification is a method that doesn't need validation or output, it simply notifies a peer for a specific information + +```json +{ "type": "notification", "result": "Hello from server" } +``` From c6184363d8586258f68cfda83717e50e426c8f6d Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Thu, 19 Feb 2026 12:58:07 +0100 Subject: [PATCH 3/3] Session server --- README.md | 14 +++++--------- examples/server.rs | 29 ++++++++--------------------- src/server.rs | 42 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 54 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index 6ec622d..7a7535a 100644 --- a/README.md +++ b/README.md @@ -66,16 +66,12 @@ impl Method for Data { let server = SessionServer::bind("127.0.0.1:8080").await?; -loop { - let session = server.accept().await; +server + .session_loop(async |session, addr| { + // This will run on every new client - session - .on::(async |_, req| { - // Response is required - Ok("Hello from server".to_string()) - }) - .await; -} + Ok(()) + }).await; ``` ## Protocol diff --git a/examples/server.rs b/examples/server.rs index 0d6fb8e..77583b1 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,7 +1,5 @@ use serde::{Deserialize, Serialize}; -use tokio::net::TcpListener; - -use session_rs::{Method, session::Session, ws::WebSocket}; +use session_rs::{Method, server::SessionServer}; #[derive(Debug, Serialize, Deserialize)] struct Data; @@ -15,23 +13,10 @@ impl Method for Data { #[tokio::main(flavor = "current_thread")] async fn main() -> session_rs::Result<()> { - let listener = TcpListener::bind("127.0.0.1:8080").await?; - println!("Server listening on ws://127.0.0.1:8080"); - - loop { - let (stream, addr) = listener.accept().await?; - println!("New connection: {}", addr); - - tokio::spawn(async move { - // Wrap session in Arc so tasks can share it - let session = Session::from_ws( - WebSocket::handshake(stream) - .await - .expect("Failed to initialize websocket"), - ); - - session.start_receiver(); + let server = SessionServer::bind("127.0.0.1:8080").await?; + server + .session_loop(async |session, _| { session .on::(async |_, req| { println!("Msg from client: {req}"); @@ -43,6 +28,8 @@ async fn main() -> session_rs::Result<()> { Ok("Hello from server".to_string()) }) .await; - }); - } + + Ok(()) + }) + .await } diff --git a/src/server.rs b/src/server.rs index 7754b94..4b4db24 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1 +1,41 @@ -pub struct SessionServer(); +use std::{net::SocketAddr, sync::Arc}; + +use tokio::net::TcpListener; + +use crate::{session::Session, ws::WebSocket}; + +pub struct SessionServer { + listener: TcpListener, +} + +impl SessionServer { + pub async fn bind(addr: &str) -> crate::Result { + Ok(Self { + listener: TcpListener::bind(addr).await?, + }) + } + + pub async fn accept(&self) -> crate::Result<(Session, SocketAddr)> { + let (stream, addr) = self.listener.accept().await?; + + let ws = WebSocket::handshake(stream).await?; + + Ok((Session::from_ws(ws), addr)) + } + + pub async fn session_loop> + Send + 'static>( + &self, + on_conn: impl Fn(Session, SocketAddr) -> Fut + 'static, + ) -> crate::Result<()> { + let conn_handler = Arc::new(on_conn); + + loop { + let (session, addr) = self.accept().await?; + let conn_handler = conn_handler.clone(); + + session.start_receiver(); + + tokio::spawn(conn_handler(session, addr)); + } + } +}