No description
Find a file
rust-connect-demo 8937104c0f Connect RPC demo in Rust: connectrpc/connect-rust + buffa, all 4 RPC modes, benchmark
- build.rs codegen from a checked-in precompiled FileDescriptorSet
  (no protoc/buf needed at build time; regenerate via buf)
- standalone hyper Server + pooled HttpClient (HTTP/1.1)
- benchmark: codec sweep 64K-2M (buffa), concurrency scaling with
  TCP conn counts, large-payload, bidi (into_split halves)
2026-09-08 20:06:08 +02:00
proto/greet/v1 Connect RPC demo in Rust: connectrpc/connect-rust + buffa, all 4 RPC modes, benchmark 2026-09-08 20:06:08 +02:00
src Connect RPC demo in Rust: connectrpc/connect-rust + buffa, all 4 RPC modes, benchmark 2026-09-08 20:06:08 +02:00
.gitignore Connect RPC demo in Rust: connectrpc/connect-rust + buffa, all 4 RPC modes, benchmark 2026-09-08 20:06:08 +02:00
build.rs Connect RPC demo in Rust: connectrpc/connect-rust + buffa, all 4 RPC modes, benchmark 2026-09-08 20:06:08 +02:00
Cargo.lock Connect RPC demo in Rust: connectrpc/connect-rust + buffa, all 4 RPC modes, benchmark 2026-09-08 20:06:08 +02:00
Cargo.toml Connect RPC demo in Rust: connectrpc/connect-rust + buffa, all 4 RPC modes, benchmark 2026-09-08 20:06:08 +02:00
README.md Connect RPC demo in Rust: connectrpc/connect-rust + buffa, all 4 RPC modes, benchmark 2026-09-08 20:06:08 +02:00

rust-connect-demo

A minimal demo of a Rust server and client speaking Connect RPC via connectrpc/connect-rust (the connectrpc crate — Tower-based, serving Connect, gRPC, and gRPC-Web clients over HTTP), with message types generated by buffa.

This is the Rust Connect counterpart of the sibling repos, all sharing the same schema, all four RPC modes, and the same benchmark methodology:

Project layout

proto/greet/v1/greet.proto           # the schema: greet.v1.GreetService (4 RPC modes)
proto/greet/v1/greet_descriptor.bin  # precompiled FileDescriptorSet (checked in)
build.rs                             # connectrpc-build codegen from the descriptor set
src/server.rs                        # Connect server (standalone hyper-based Server)
src/client.rs                        # Connect client (HttpClient with connection pooling)
src/benchmark.rs                     # performance benchmark (codec + RPC throughput)

Dependencies (Cargo.toml)

Dependency Why it's here
connectrpc (features client, server) Connect runtime: Tower-based handlers, standalone hyper server, pooled HTTP client.
connectrpc-build (build) build.rs codegen — service traits, clients, and message types in one pass.
buffa, buffa-types Message types: generated structs derive buffa's wire-format traits (and serde for the JSON codec).
serde, serde_json, http, http-body Required by the generated code's bounds (JSON codec / HTTP types).
tokio, futures Async runtime and stream combinators.

Codegen without protoc

build.rs reads a precompiled FileDescriptorSet (greet_descriptor.bin, checked in), so neither protoc nor buf is needed at build time. Regenerate the descriptor after changing greet.proto (needs any buf binary, e.g. from a sibling pyrpc-demo venv):

/path/to/pyrpc-demo/.venv/bin/buf build -o proto/greet/v1/greet_descriptor.bin --as-file-descriptor-set

Note: our proto uses syntax = "proto3" (implicit presence), so generated scalars are plain String/i32 — not Option — and views borrow &str zero-copy from the request buffer.

Run

cargo run --release --bin server   # terminal 1
cargo run --release --bin client   # terminal 2

Expected client output (all four RPC kinds):

--- unary: Greet ---
[client] Hello, world!

--- server streaming: GreetStream ---
[client] Hello #1, streamer!
[client] Hello #2, streamer!
[client] Hello #3, streamer!

--- client streaming: GreetCount ---
[client] server counted 3 requests

--- bidirectional streaming: GreetChat ---
[client] Hi, alice!
[client] Hi, bob!
[client] Hi, carol!

Benchmark

Same methodology as the sibling repos. HttpClient::plaintext() is hyper's pooled HTTP/1.1 client — the direct counterpart of pyrpc-demo's pyqwest client — so the concurrency sweep shows the HTTP/1.1 fingerprint (one socket per in-flight request). (connect-rust also offers h2c and TLS-h2 transports; not benchmarked here.)

  1. Codec — in-process serialize/deserialize of GreetRequest at 64 / 128 / 256 / 512 / 1024 / 2048 KiB payloads (buffa wire format), each timed ≥0.5 s per direction.
  2. Unary throughput vs concurrency — 2,000 Greet round-trips at each concurrency level (1, 4, 16, 64), with live TCP connection counts (via netstat; note it sees both ends of loopback connections, so counts read roughly 2× the real connection count plus pool churn).
  3. Unary throughput, large payloads — 200 round-trips, 2 MiB payloads.
  4. Bidi streaming throughput — 5,000 messages over one GreetChat stream (split into send/receive halves via into_split()).
cargo run --release --bin server       # terminal 1
cargo run --release --bin benchmark    # terminal 2

Sample results (M-series MacBook, 2026-09)

concurrency req/s TCP conns (netstat, ~2× real)
1 20,844 2
4 42,111 8
16 91,941 36
64 117,887 170
Benchmark this repo Connect/pyqwest (py) gRPC/tonic (Rust)
unary 2 MiB (c=8) 937 req/s (1,966 MB/s) 189 req/s (397 MB/s) 626 req/s (1,313 MB/s)
bidi streaming (1 KiB) 90.5k msg/s 18.2k msg/s 572k msg/s

Observations:

  • Fastest Connect stack in the family on HTTP/1.1: 118k req/s at c=64 (~9× the Python Connect client) and ~2 GB/s on 2 MiB payloads — the protocol's per-request HTTP/1.1 handling costs little when it's all Rust.
  • Still HTTP/1.1: throughput is bought with sockets like every other HTTP/1.1 stack, and single-stream bidi (90.5k msg/s) trails tonic's HTTP/2 (572k msg/s).
  • Codec: buffa's wire format is on par with prost on serialize and notably faster on deserialize (zero-copy views).

See ../pyrpc-demo's README (at git.burakemir.ch/burak/pyrpc-demo) for the cross-language results table and charts.