pub struct GrpcDispatcher { /* private fields */ }Expand description
gRPC dispatcher that routes A2A requests to a RequestHandler.
Serves the canonical lf.a2a.v1.A2AService (protobuf-native, wire
compatible with the official A2A SDKs). That is the only gRPC surface:
the deprecated pre-0.7 JSON-tunnel service, and the grpc-legacy-json
feature that gated it, were removed in 0.8.
Create via GrpcDispatcher::new and serve with GrpcDispatcher::serve
or build a tonic service with GrpcDispatcher::into_service.
Implementations§
Source§impl GrpcDispatcher
impl GrpcDispatcher
Sourcepub const fn new(
handler: Arc<RequestHandler>,
config: GrpcConfig,
) -> GrpcDispatcher
pub const fn new( handler: Arc<RequestHandler>, config: GrpcConfig, ) -> GrpcDispatcher
Creates a new gRPC dispatcher wrapping the given handler.
Sourcepub const fn with_max_connections(self, max: usize) -> GrpcDispatcher
pub const fn with_max_connections(self, max: usize) -> GrpcDispatcher
Caps the connections served at once. Default: unbounded.
The same knob the WebSocket binding has had since 2026-08-19
(with_max_connections),
and the gap the measurement under with_http2_keepalive
left open: keepalive closes a peer that stops answering, and nothing
bounded how many peers could be answering at once. tonic’s server has
no connection ceiling of its own — concurrency_limit is per
connection — so this one is enforced in front of it: the permit is
taken before accept(), and load past the ceiling waits in the
kernel’s listen backlog until a served connection ends, or is refused
by the kernel when that fills. The permit travels with the accepted
socket and is released when tonic drops it, so it covers TLS, HTTP/2
and the RPCs on it alike.
Applied by every serve* method. into_service
hands the service to a server the caller runs, and that server’s
accept loop is the caller’s to bound.
Sourcepub fn with_tls(self, tls: ServerTlsConfig) -> GrpcDispatcher
pub fn with_tls(self, tls: ServerTlsConfig) -> GrpcDispatcher
Serves TLS on the listener itself instead of relying on a terminating proxy. Default: off (plaintext).
tls carries the server certificate and key (ServerTlsConfig::new() .identity(Identity::from_pem(cert, key))) and, for mutual TLS, the CA
that client certificates must chain to (.client_ca_root(..), with
.client_auth_optional(true) to admit clients that present none).
The types are re-exported from dispatch::grpc so no tonic
dependency is needed. Applied by every serve* method and by nothing
else: into_service hands the service to a
tonic::transport::Server the caller configures, TLS included.
The listener then speaks TLS only. A plaintext client is refused at
the handshake; there is no fallback, for the same reason the client’s
grpc-tls transport refuses to downgrade.
tonic builds the acceptor from the process-level rustls crypto
provider. If none is installed when the listener is built — the case
for a binary that links both ring and aws-lc-rs, which this
workspace’s --all-features build does — ring is installed as the
default, once, rather than letting rustls panic. An application that
wants a different provider installs it before serving.
A configuration tonic rejects (a key that does not match its
certificate, an unparsable PEM) is reported by the serve* call as an
std::io::Error, not a panic.
Sourcepub const fn with_http2_keepalive(
self,
interval: Duration,
timeout: Duration,
) -> GrpcDispatcher
pub const fn with_http2_keepalive( self, interval: Duration, timeout: Duration, ) -> GrpcDispatcher
Sends an HTTP/2 PING every interval on an idle connection and closes
it if no answer arrives within timeout. Default: off.
GrpcConfig bounds message size and per-connection concurrency, and
nothing bounded a connection that is simply there. MEASURED
2026-08-19: 400 TCP connections opened against this dispatcher and left
silent were all accepted and all still alive twelve seconds later. The
ceiling is the process’s file-descriptor table — the same measurement,
and the same shape, as the WebSocket dispatcher before
with_idle_timeout.
HTTP/2 keepalive is the gRPC-native answer to it, and it is better than a plain idle timeout for the same reason the WebSocket knob pings: a conformant client’s HTTP/2 stack answers a PING without the application being involved, so this closes peers that are unresponsive rather than peers that are merely quiet. A streaming RPC that is waiting for its next event is quiet and healthy, and this leaves it alone.
Off by default, matching this workspace’s other connection knobs: a
deployment may have clients that are configured to object to PINGs, and
choosing an interval for someone is choosing their traffic profile.
(Duration::from_secs(30), Duration::from_secs(10)) is a conventional
starting point.
Sourcepub const fn with_max_connection_age(self, age: Duration) -> GrpcDispatcher
pub const fn with_max_connection_age(self, age: Duration) -> GrpcDispatcher
Closes a connection once it has been open for age, letting the client
reconnect. Default: off.
Distinct from with_http2_keepalive,
which detects a peer that has stopped answering. This one bounds a peer
that answers perfectly and simply never leaves — which is what makes a
fleet behind a load balancer drift into imbalance, since a gRPC client
pins one connection and keeps it.
tonic sends GOAWAY and drains in-flight RPCs rather than cutting them, so this is a reconnect rather than a failure.
Sourcepub const fn with_completion_grace(self, grace: Duration) -> GrpcDispatcher
pub const fn with_completion_grace(self, grace: Duration) -> GrpcDispatcher
How long in-flight tasks get to finish on their own once
serve_with_shutdown’s signal fires,
before they are cancelled. Default
DEFAULT_COMPLETION_GRACE;
the HTTP server’s ServeConfig::with_completion_grace
is the same setting.
Sourcepub const fn with_task_grace(self, grace: Duration) -> GrpcDispatcher
pub const fn with_task_grace(self, grace: Duration) -> GrpcDispatcher
How long cancelled tasks get to act on their cancellation before the
drain starts. Default DEFAULT_TASK_GRACE.
Sourcepub const fn with_drain_timeout(self, timeout: Duration) -> GrpcDispatcher
pub const fn with_drain_timeout(self, timeout: Duration) -> GrpcDispatcher
How long serve_with_shutdown waits for
connections to close after the tasks have ended. Default
DEFAULT_DRAIN_TIMEOUT.
Sourcepub async fn serve(self, addr: impl ToSocketAddrs) -> Result<(), Error>
pub async fn serve(self, addr: impl ToSocketAddrs) -> Result<(), Error>
Starts a gRPC server on the given address.
Blocks until the server shuts down. Uses the configured message size limits and concurrency settings.
It takes no shutdown signal; use
serve_with_shutdown for a graceful
stop.
§Errors
Returns std::io::Error if binding fails.
Sourcepub async fn serve_with_addr(
self,
addr: impl ToSocketAddrs,
) -> Result<SocketAddr, Error>
pub async fn serve_with_addr( self, addr: impl ToSocketAddrs, ) -> Result<SocketAddr, Error>
Starts a gRPC server and returns the bound SocketAddr.
Like serve, but returns the address immediately
and runs the server in a background task. Useful for tests.
§Errors
Returns std::io::Error if binding fails.
Sourcepub fn serve_with_listener(
self,
listener: TcpListener,
) -> Result<SocketAddr, Error>
pub fn serve_with_listener( self, listener: TcpListener, ) -> Result<SocketAddr, Error>
Starts a gRPC server on a pre-bound TcpListener.
This is the recommended approach when you need to know the server address before constructing the handler (e.g., for agent cards with correct URLs). Pre-bind the listener, extract the address, build your handler, then pass the listener here.
Returns the local address and runs the server in a background task.
§Errors
Returns std::io::Error if the listener’s local address cannot be read.
Sourcepub fn into_service(&self) -> A2aServiceServer<A2aServiceImpl>
pub fn into_service(&self) -> A2aServiceServer<A2aServiceImpl>
Builds the canonical tonic service for use with a custom server setup.
Returns an A2aServiceServer (the lf.a2a.v1.A2AService binding)
that can be added to a tonic::transport::Server via add_service.
It is the only gRPC service this dispatcher serves; the pre-0.7
JSON-tunnel companion was removed in 0.8.
Source§impl GrpcDispatcher
impl GrpcDispatcher
Sourcepub async fn serve_with_shutdown(
self,
listener: TcpListener,
shutdown: impl Future<Output = ()> + Send,
) -> Result<ServeReport, Error>
pub async fn serve_with_shutdown( self, listener: TcpListener, shutdown: impl Future<Output = ()> + Send, ) -> Result<ServeReport, Error>
Serves until shutdown resolves, then ends the work in flight and
drains, the way Server::serve_with_shutdown
does for the HTTP bindings:
- Stop accepting. The listener is closed, and every open
connection is sent an HTTP/2
GOAWAY, so it finishes the calls it has and starts no new ones. That is one step earlier than the HTTP server, which keeps admitting requests on open connections through step 2; it is how tonic stops. - End in-flight tasks with
RequestHandler::finish_in_flight: up towith_completion_gracefor them to finish on their own, then their cancellation tokens fire and executors getwith_task_graceto act on it. A stream whose task ends gets its terminal event. - Drain, for up to
with_drain_timeout.
Call RequestHandler::shutdown
after it returns. Dropping the returned future stops the server
without any of this.
It takes a bound listener, as
serve_with_listener does, so the caller
knows the address — port 0 included — before serving starts.
§Errors
A TLS configuration tonic rejects, or the error the server stopped
with. serve_with_listener discards the
latter; this returns it.
Trait Implementations§
Auto Trait Implementations§
impl !RefUnwindSafe for GrpcDispatcher
impl !UnwindSafe for GrpcDispatcher
impl Freeze for GrpcDispatcher
impl Send for GrpcDispatcher
impl Sync for GrpcDispatcher
impl Unpin for GrpcDispatcher
impl UnsafeUnpin for GrpcDispatcher
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
impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
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