Make the protocol transport-agnostic and use tokio-tungstenite

Replace the hand-rolled WebSocket implementation with a transport
boundary (Frame over any Sink/Stream) plus adapters for tokio-tungstenite
(server/client features) and axum (axum feature). The JSON wire format
is unchanged, so existing peers keep working.

Fixes:
- unbounded frame lengths were allocated up front; the server now
  enforces message/frame size limits (1 MiB default)
- responses arriving before `request` subscribed were lost
- an accept() error ended `session_loop`
- requests sent right after connecting could arrive before handlers
  were registered; the receiver now starts after `on_conn` returns
- panics in the receive loop skipped `on_close` and leaked sessions;
  `on_close` now runs exactly once and handler panics fail only their
  request
- unknown methods and invalid data got no reply; they now get an
  error response
- slow handlers blocked pongs and responses

Adds `on_notification`, `request_timeout`, `closed`, `is_closed`, `id`,
`ServerConfig`, `from_transport`, `from_tungstenite` and `from_axum`,
integration tests, and an axum example. Bumps to 0.2.0 since
`Session::connect` now takes a URL and the `ws` module is removed.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-09-25 05:35:57 +02:00
co-authored by claude
parent dd83ae219a
commit d11d4ea08b
16 changed files with 2230 additions and 804 deletions
+41
View File
@@ -0,0 +1,41 @@
//! The same server as `examples/server.rs`, mounted in an axum router next to
//! ordinary HTTP routes.
use axum::{Router, extract::WebSocketUpgrade, response::Response, routing::get};
use serde::{Deserialize, Serialize};
use session_rs::{Method, Session};
#[derive(Debug, Serialize, Deserialize)]
struct Data;
impl Method for Data {
const NAME: &'static str = "data";
type Request = String;
type Response = String;
type Error = String;
}
async fn ws(upgrade: WebSocketUpgrade) -> Response {
upgrade.max_message_size(1 << 20).on_upgrade(async |socket| {
let session = Session::from_axum(socket);
session
.on_request::<Data, _>(async |_, req| {
println!("Msg from client: {req}");
Ok(format!("Hello from axum, you said {req:?}"))
})
.await;
session.start_receiver();
})
}
#[tokio::main]
async fn main() -> std::io::Result<()> {
let app = Router::new()
.route("/", get(ws))
.route("/health", get(async || "ok"));
let listener = tokio::net::TcpListener::bind("127.0.0.1:8080").await?;
axum::serve(listener, app).await
}