Skip to main content

grpc_webnext_client/
lib.rs

1//! # grpc-webnext-client
2//!
3//! A gRPC client for **Rust WASM frontends**, speaking real gRPC to a
4//! [grpc-webnext](https://github.com/debdattabasu/grpc-webnext) endpoint over an
5//! [h2ts](https://github.com/debdattabasu/h2ts) WebSocket tunnel.
6//!
7//! **No tonic, no hyper, no tokio.** The wire is real HTTP/2 — trailers,
8//! multiplexing, flow control — so there is nothing to translate: framing plus a
9//! status read off the trailers is the whole client. Everything is single-threaded
10//! (`Rc`, `!Send`), which is what a browser actually is; nothing here asserts
11//! `Send` to satisfy a runtime that does not exist on this target.
12//!
13//! ```no_run
14//! # async fn demo(client: grpc_webnext_client::Client) -> Result<(), grpc_webnext_client::Status> {
15//! use grpc_webnext_client::CallOptions;
16//!
17//! let reply = client
18//!     .unary("/helloworld.Greeter/SayHello", encoded_request(), CallOptions::new())
19//!     .await?;
20//! let _ = reply.message; // your codec decodes these bytes
21//! # Ok(()) }
22//! # fn encoded_request() -> Vec<u8> { Vec::new() }
23//! ```
24//!
25//! ## Codec
26//!
27//! The client deals in **message bytes**, so it is codec-agnostic: encode with
28//! `prost`, or anything else. The `prost` feature adds typed helpers over
29//! [`prost::Message`]; there is no generated service stub layer yet, so a service
30//! is a handful of thin wrappers over [`Client::unary`] and friends.
31//!
32//! ## Streaming
33//!
34//! All four cardinalities. Backpressure on the response is real and costs nothing
35//! here: `h2ts-client` replenishes the HTTP/2 receive window only as the body is
36//! polled, so a consumer that stops reading stops the *server* rather than filling
37//! memory — the same property the TypeScript client gets, for the same reason.
38//!
39//! Dropping a [`Streaming`] cancels the RPC: the HTTP/2 stream is reset, so the
40//! server stops work it has no reader for. That is also how a deadline stops a
41//! stream, and why [`CallOptions::timeout`] covers a stream's whole lifetime rather
42//! than only its opening.
43//!
44//! The streaming cardinalities need `h2ts-client` **0.1.2**: at 0.1.1
45//! `Response::into_body` took `self` while `trailers()` needed `&self`, so a caller
46//! could stream the body or read the trailers, never both — and gRPC's terminal
47//! status lives in the trailers. A streaming client built on 0.1.1 could not tell a
48//! failed stream from a successfully empty one. `Response::into_parts` fixes that.
49//!
50//! ## Reconnect
51//!
52//! [`Client`] is a gRPC **channel**, not a handle to a socket: the tunnel opens on
53//! the first call and **reopens if it drops**, the way `tonic::transport::Channel`
54//! does. The call that discovers a dead tunnel reports the failure and the next one
55//! reconnects — the transport never silently replays a request the server may
56//! already have seen, because that is a retry policy decision and not its to make.
57//!
58//! [`Client::state`] and [`Client::state_changes`] surface where the channel is
59//! (gRPC's connectivity states, `WaitForStateChange` as a stream), so an app can
60//! say something useful instead of guessing from a failed call.
61//!
62//! There is no reconnect **backoff**: like tonic, a redial happens when a call asks
63//! for one, so the call rate bounds the dial rate. A client built with
64//! [`Client::over_transport`] cannot redial at all — the transport is consumed —
65//! and says so rather than pretending to be live.
66
67mod client;
68mod codec;
69mod metadata;
70mod state;
71mod status;
72mod url;
73
74pub use client::{CallOptions, Client, Streaming, UnaryResponse};
75pub use client::Connector;
76pub use state::ConnectivityState;
77pub use codec::{encode_message, Deframer};
78pub use metadata::{Metadata, MetadataValue};
79pub use status::{Code, Status};
80
81// Re-exported so callers can tune the tunnel — or supply their own — without
82// depending on h2ts-client directly. `TransportError` belongs here too: a caller
83// building a `Transport` has to name the error type its sink produces, so leaving it
84// out meant `Client::over_transport` could not be used without adding h2ts-client as
85// a second dependency.
86pub use h2ts_client::{ConnectOptions, Transport, TransportError};
87
88/// The WebSocket subprotocol an h2ts client offers.
89pub const H2TS_SUBPROTOCOL: &str = h2ts_client::DEFAULT_SUBPROTOCOL;
90
91#[cfg(target_arch = "wasm32")]
92mod web;
93#[cfg(target_arch = "wasm32")]
94pub use web::connect;
95
96#[cfg(feature = "prost")]
97mod typed;
98#[cfg(feature = "prost")]
99pub use typed::{TypedClient, TypedResponse};