pub struct WebSocketDispatcher { /* private fields */ }Expand description
WebSocket-based A2A dispatcher.
Accepts WebSocket connections and processes JSON-RPC 2.0 messages over the WebSocket channel. Streaming responses are sent as individual text frames.
Incoming messages are capped at 4 MiB at the WebSocket protocol level; a connection sending a larger message or frame is terminated.
§Authentication, tenancy, and headers
The HTTP headers of the upgrade request that establishes the connection
(lowercased, plus the request path under ":path") are captured during the
handshake and passed to the handler for every request on the
connection. Tenant resolvers and interceptors therefore see the same header
context they would on the HTTP bindings — credentials are presented once,
at connect time, and apply to the whole connection.
An upgrade request carrying an A2A-Version header with a major version
other than 1 is rejected during the handshake with HTTP 400.
Implementations§
Source§impl WebSocketDispatcher
impl WebSocketDispatcher
Sourcepub const fn new(handler: Arc<RequestHandler>) -> WebSocketDispatcher
pub const fn new(handler: Arc<RequestHandler>) -> WebSocketDispatcher
Creates a new WebSocket dispatcher.
Sourcepub const fn accept_missing_version_header(self) -> WebSocketDispatcher
pub const fn accept_missing_version_header(self) -> WebSocketDispatcher
Accepts upgrade requests without an A2A-Version header.
Spec §3.6.2 interprets a missing/empty header as protocol 0.3, which this server does not implement, so the strict default rejects such handshakes (parity with the HTTP dispatchers). This opt-out restores the tolerant pre-0.7 behavior.
Sourcepub const fn with_handshake_timeout(
self,
timeout: Duration,
) -> WebSocketDispatcher
pub const fn with_handshake_timeout( self, timeout: Duration, ) -> WebSocketDispatcher
Overrides the handshake timeout (default: 10 seconds).
A peer that does not complete the WebSocket handshake within this bound is disconnected.
Sourcepub const fn with_max_connections(self, max: usize) -> WebSocketDispatcher
pub const fn with_max_connections(self, max: usize) -> WebSocketDispatcher
Caps the connections served at once. Default: unbounded.
Without this the accept loop spawns a task per accepted socket with no
ceiling, exactly as
serve did before ServeConfig existed.
Measured on 2026-08-19: 400 idle handshaken connections were accepted
and held, none refused, the ceiling being the process’s file-descriptor
table.
The permit is taken before accept(), so load past the ceiling
waits in the kernel’s listen backlog and is refused by the kernel when
that fills — a far better failure than an unbounded task spawn that
turns a traffic spike into an OOM.
Unbounded stays the default for the same reason it does on
ServeConfig::max_connections: a
deployment may genuinely want no ceiling, and picking one for it is
picking its capacity.
Sourcepub const fn with_idle_timeout(self, timeout: Duration) -> WebSocketDispatcher
pub const fn with_idle_timeout(self, timeout: Duration) -> WebSocketDispatcher
Closes a connection that carries no traffic in either direction for
timeout. Default: off.
with_handshake_timeout bounds a peer
that connects and never upgrades. Nothing bounded the peer that
completes the handshake and then goes silent, so one that did held a
task, a socket and a file descriptor for the life of the process —
measured, a connection idle for 12 seconds was still being served, and
the read loop has no bound at all.
§Why this is off by default when the HTTP one is on
On HTTP, silence means nothing is happening. On a WebSocket it may mean a subscription is waiting for its next event, which is a legitimate thing to do for hours. A timeout defaulted on would close healthy subscriptions, and a knob that breaks correct programs is a knob nobody turns on.
What makes it safe to turn on: at the halfway point of the budget the server sends a WebSocket Ping. Every conformant client library — this SDK’s included, via tungstenite — answers automatically, and that Pong is traffic. So the timeout closes peers that are unresponsive, not peers that are merely quiet. Only a client that has stopped reading its socket, or gone away without a close frame, fails to answer.
Outbound frames count too, so a stream pushing events to a silent consumer keeps its own connection alive.
DEFAULT_WS_IDLE_TIMEOUT (75s, matching the HTTP default) is a
reasonable starting point.
Sourcepub async fn serve(
self: Arc<WebSocketDispatcher>,
addr: impl ToSocketAddrs,
) -> Result<(), Error>
pub async fn serve( self: Arc<WebSocketDispatcher>, addr: impl ToSocketAddrs, ) -> Result<(), Error>
Starts a WebSocket server on the given address.
The accept loop never terminates on transient accept() errors
(per-connection aborts, fd-table exhaustion) — it logs, backs off when
the fd table is full, and keeps accepting.
§Errors
Returns std::io::Error if the TCP listener fails to bind.
Sourcepub async fn serve_with_addr(
self: Arc<WebSocketDispatcher>,
addr: impl ToSocketAddrs,
) -> Result<SocketAddr, Error>
pub async fn serve_with_addr( self: Arc<WebSocketDispatcher>, addr: impl ToSocketAddrs, ) -> Result<SocketAddr, Error>
Starts a WebSocket server and returns the bound address.
Like serve, but useful for tests (bind to port 0).
§Errors
Returns std::io::Error if the TCP listener fails to bind.
Auto Trait Implementations§
impl !RefUnwindSafe for WebSocketDispatcher
impl !UnwindSafe for WebSocketDispatcher
impl Freeze for WebSocketDispatcher
impl Send for WebSocketDispatcher
impl Sync for WebSocketDispatcher
impl Unpin for WebSocketDispatcher
impl UnsafeUnpin for WebSocketDispatcher
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> FutureExt for T
impl<T> FutureExt for T
Source§fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
fn with_context(self, otel_cx: Context) -> WithContext<Self> ⓘ
Source§fn with_current_context(self) -> WithContext<Self> ⓘ
fn with_current_context(self) -> WithContext<Self> ⓘ
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
fn instrument(self, span: Span) -> Instrumented<Self> ⓘ
Source§fn in_current_span(self) -> Instrumented<Self> ⓘ
fn in_current_span(self) -> Instrumented<Self> ⓘ
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
fn into_either(self, into_left: bool) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self> ⓘ
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§impl<T> IntoRequest<T> for T
impl<T> IntoRequest<T> for T
Source§fn into_request(self) -> Request<T>
fn into_request(self) -> Request<T>
T in a tonic::Request