# After 30 Minutes: What to Try Next

> Synthesis of what you now understand — the codegen pipeline, Tower dispatch, interceptor chain, and protocol demux — plus concrete next actions: run the conformance suite, walk the examples (eliza, multiservice, streaming-tour, wasm-client), read docs/guide.md sections on TLS and streaming, and understand how task ci gates every PR.

- Repository: anthropics/connect-rust
- GitHub: https://github.com/anthropics/connect-rust
- Human wiki: https://grok-wiki.com/public/wiki/anthropics-connect-rust-abe117693c52
- Complete Markdown: https://grok-wiki.com/public/wiki/anthropics-connect-rust-abe117693c52/llms-full.txt

## Source Files

- `docs/guide.md`
- `Taskfile.yaml`
- `examples/streaming-tour/src`
- `examples/multiservice/src`
- `examples/wasm-client/src`
- `conformance/Cargo.toml`
- `CHANGELOG.md`

---

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [docs/guide.md](docs/guide.md)
- [Taskfile.yaml](Taskfile.yaml)
- [examples/streaming-tour/src/server.rs](examples/streaming-tour/src/server.rs)
- [examples/streaming-tour/src/client.rs](examples/streaming-tour/src/client.rs)
- [examples/multiservice/src/bin/server.rs](examples/multiservice/src/bin/server.rs)
- [examples/wasm-client/src/transport.rs](examples/wasm-client/src/transport.rs)
- [conformance/Cargo.toml](conformance/Cargo.toml)
- [CHANGELOG.md](CHANGELOG.md)
- [.github/workflows/ci.yml](.github/workflows/ci.yml)
</details>

# After 30 Minutes: What to Try Next

By this point you have a mental map of the three-crate layout (`connectrpc`, `connectrpc-codegen`, `connectrpc-build`), you have seen a unary handler compile and serve, and you have a sense of where the major moving parts live. This page consolidates what those parts actually do — the codegen pipeline, the Tower dispatch stack, the interceptor chain, and the protocol demultiplexer — then gives you a concrete sequence of next actions: running the conformance suite, walking each example, and understanding how `task ci` gates every PR.

---

## What You Now Understand

### The Codegen Pipeline

Two workflows produce identical runtime APIs. Both ultimately invoke `protoc-gen-connect-rust` (the binary crate inside `connectrpc-codegen`) with a `buf`-based pipeline:

```
.proto files
    │
    ├─ protoc-gen-buffa           → <stem>.rs, <stem>.__view.rs   (message types + zero-copy views)
    ├─ protoc-gen-connect-rust    → <stem>.__connect.rs            (service trait + client struct)
    └─ protoc-gen-buffa-packaging → <pkg>.mod.rs                   (include! stitcher)
```

`connectrpc-build` wraps this in a `build.rs` call that writes into `OUT_DIR` and re-triggers on change. The `buf generate` path writes checked-in files that you import with an explicit `#[path = "generated/..."]` declaration. The output for both is a service trait (e.g. `trait GreetService`) and a generated client (`GreetServiceClient`), with the same call-site shape either way.

The `multiservice` example is the best live view of the `buf generate` output layout — two separate `generated/buffa/` and `generated/connect/` trees, one for message types and one for service stubs, stitched together by `mod.rs` files.

Sources: [docs/guide.md:172-249](), [examples/multiservice/src](examples/multiservice/src/bin/server.rs)

---

### Tower Dispatch

Every `connectrpc::Router` is a `tower::Service`. The request path, from wire to handler, is:

```text
TCP / TLS
  └── hyper (HTTP/1.1 or HTTP/2)
        └── Tower layer stack (TraceLayer, auth middleware, TimeoutLayer, …)
              └── ConnectRpcService    ← demux: detects Connect / gRPC / gRPC-Web
                    └── Interceptor chain  ← typed, after envelope decode
                          └── Router::dispatch  ← matches /package.Service/Method
                                └── your handler async fn
```

`ServiceBuilder` applies layers top-to-bottom so the first `.layer()` call is outermost (sees requests first, responses last). A typical axum composition looks like:

```rust
// docs/guide.md:599-609
let app = axum::Router::new()
    .fallback_service(connect_router.into_axum_service())
    .layer(
        ServiceBuilder::new()
            .layer(TraceLayer::new_for_http())          // outermost
            .layer(axum::middleware::from_fn_with_state(tokens, auth_middleware))
            .layer(TimeoutLayer::with_status_code(
                http::StatusCode::REQUEST_TIMEOUT,
                Duration::from_secs(5),
            )),                                         // innermost
    );
```

Tower middleware operates on raw `http::Request` / `http::Response` bytes — it runs before the envelope is decoded. Middleware inserts values via `req.extensions_mut().insert(value)` and handlers read them back via `ctx.extensions().get::<T>()`.

Sources: [docs/guide.md:577-639]()

---

### The Interceptor Chain

Interceptors (added in 0.6.0) run after the dispatcher decodes the envelope, decompresses the body, and parses protocol headers — so they already know the RPC method, the deadline, and the negotiated protocol (Connect / gRPC / gRPC-Web):

```text
ConnectRpcService
  ├── protocol demux + envelope decode
  ├── Interceptor A  ← sees Spec, headers, deadline, Payload (lazily decoded)
  │     └── Interceptor B
  │           └── handler
  └── (response flows back through A ← B ← handler)
```

Registration order is outermost-first, matching `connect-go`'s `WithInterceptors`:

```rust
// docs/guide.md:760-770
.with_interceptor(A).with_interceptor(B)
//  request:  A → B → handler
//  response: A ← B ← handler
```

Key design details:
- **Zero-cost fast path.** A service with no interceptors pays one `is_empty()` branch and allocates nothing per request.
- **Lazy decode.** `Payload::message::<M>()` decodes once and caches — if an interceptor decodes, the handler's decode is free.
- **Shared instances.** `with_interceptor_arc(Arc<dyn Interceptor>)` shares a process-wide interceptor (token cache, rate limiter) across multiple services.
- **Streaming hook.** `intercept_streaming` covers all three non-unary shapes (server, client, bidi) with a single `Stream`-shaped hook at stream establishment.

Sources: [docs/guide.md:718-888](), [CHANGELOG.md:11-75]()

---

### Protocol Demultiplexer

`ConnectRpcService` inspects incoming requests and routes them to the appropriate codec path. Three wire protocols are supported on the same port:

| Protocol | Content-Type header | `ctx.protocol()` value |
|---|---|---|
| Connect (unary) | `application/proto` or `application/json` | `Connect` |
| gRPC | `application/grpc` | `Grpc` |
| gRPC-Web | `application/grpc-web` | `GrpcWeb` |

Handlers and interceptors never branch on protocol — the dispatcher handles all encoding, framing, error serialization (Connect JSON vs gRPC trailers vs gRPC-Web trailers), and compression negotiation. `ctx.protocol()` is available for observability spans that need to label `rpc.system` correctly.

Sources: [docs/guide.md:289-309](), [CHANGELOG.md:100-118]()

---

## Concrete Next Actions

### 1. Run the Conformance Suite

The conformance suite is the mechanized proof that the implementation speaks the Connect, gRPC, and gRPC-Web protocols correctly. A healthy run ends with `N passed, 0 failed`.

```bash
# One-time setup
task conformance:download
task conformance:build

# Full server suite (3 600 tests, all protocols)
task conformance:test

# Focused suites
task conformance:test-connect-only          # 1 192 tests
task conformance:test-client-connect-only   # 2 580 tests
task conformance:test-client-grpc-only      # 1 454 tests
task conformance:test-client-grpc-web-only  # 2 838 tests
```

CI runs the `connect-only` server suite and the `client-connect-only` client suite (`.github/workflows/ci.yml:120-146`). Running the full `task conformance:test` locally is the broadest protocol-correctness check you have.

The conformance binary itself (`conformance-server` and `conformance-client`) lives in `conformance/src/bin/` and links against `connectrpc` with the `server`, `axum`, and `tls` features — it is the most complete exerciser of the runtime short of your own production service.

Sources: [Taskfile.yaml:164-264](), [conformance/Cargo.toml:30](), [.github/workflows/ci.yml:120-146]()

---

### 2. Walk the Examples

Run them in this order — each one adds one concept:

#### `streaming-tour` — all four RPC types in ~130 lines

```bash
cargo run -p streaming-tour-example --bin streaming-tour-server &
cargo run -p streaming-tour-example --bin streaming-tour-client
```

The server (`examples/streaming-tour/src/server.rs`) implements `NumberService` with four methods: `square` (unary), `range` (server stream via `futures::stream::iter`), `sum` (client stream draining with `requests.next().await`), and `running_sum` (bidi via `futures::stream::unfold`). The client (`examples/streaming-tour/src/client.rs`) demonstrates all four call patterns: `.await?` for unary, `.message().await?` loop for server-stream, a `Vec` of messages for client-stream, and `.send()` / `.message()` / `.close_send()` for bidi.

Sources: [examples/streaming-tour/src/server.rs:40-129](), [examples/streaming-tour/src/client.rs:22-90]()

#### `multiservice` — multiple proto packages, multiple services, well-known types

```bash
task example:multiservice:server &   # in one terminal
task example:multiservice:client     # in another
# or end-to-end:
task example:multiservice:test
```

This is the example CI runs (`ci.yml:114`). Three services (`GreetService`, `MathService`, `WellKnownTypesService`) from separate proto packages register onto a single `ConnectRouter`, then mount on axum alongside a `/health` HTTP route. It shows the full `buf generate` output layout and `buffa_types` well-known-type usage (`Timestamp`, `Duration`, `Struct`).

Sources: [examples/multiservice/src/bin/server.rs:225-246](), [Taskfile.yaml:136-143]()

#### `eliza` — production-shaped app with TLS, mTLS, CORS, and cross-language interop

```bash
task example:eliza
```

This is the most complete example. It ports the `connectrpc/examples-go` ELIZA chatbot: server-streaming `Introduce` plus bidi-streaming `Converse`, both with TLS. The README walks through generating self-signed certs with `openssl`, enabling mTLS with `--client-ca`, and the rustls constraint that the CA cert must be distinct from the leaf cert. It interoperates with the live Go reference at `demo.connectrpc.com`.

Sources: [docs/guide.md:1269]()

#### `wasm-client` — browser fetch transport

The wasm example replaces `HttpClient` with a hand-rolled `ClientTransport` that bridges the browser `fetch` API (`web-sys`) into the `tower::Service` shape the generated client expects. The key file is `examples/wasm-client/src/transport.rs`: `FetchTransport` implements `ClientTransport` and wraps the fetch future in `SendWrapper` to satisfy the `Send` bound in a single-threaded wasm environment. The same generated `NumberServiceClient` compiles to `wasm32-unknown-unknown` without changes.

```rust
// examples/wasm-client/src/transport.rs:83-95
impl ClientTransport for FetchTransport {
    type ResponseBody = http_body_util::Full<Bytes>;
    type Error = FetchError;

    fn send(&self, request: Request<ClientBody>) -> BoxFuture<'static, ...> {
        Box::pin(SendWrapper::new(fetch(request)))
    }
}
```

Sources: [examples/wasm-client/src/transport.rs:83-95]()

---

### 3. Read the TLS and Deadline Sections of `docs/guide.md`

Two sections repay close reading because the options are non-obvious and the defaults are permissive:

**TLS** (`docs/guide.md:935-978`): The standalone `Server` and the axum path use different entry points (`Server::with_tls` vs `connectrpc::axum::serve_tls`), but both stamp `PeerAddr` and `PeerCerts` into request extensions so `ctx.peer_certs()` is portable across hosting paths. The `mtls-identity` example shows identity extraction from the cert's DNS SAN.

**Deadline policy** (`docs/guide.md:982-1040`): By default the server trusts whatever timeout the client sends — including no timeout at all. `DeadlinePolicy::with_max` is the most critical knob for any service that accepts untrusted callers. `with_default_timeout` covers requests with no timeout header. `with_enforce_on_streams(true)` closes the gap for streaming responses.

```rust
// docs/guide.md:995-1002
let policy = DeadlinePolicy::new()
    .with_min(Duration::from_millis(5))
    .with_max(Duration::from_secs(30))
    .with_default_timeout(Duration::from_secs(10))
    .with_enforce_on_streams(true);
```

Sources: [docs/guide.md:935-978](), [docs/guide.md:982-1040]()

---

### 4. Understand `task ci` — the PR Gate

`task ci` runs the exact checks CI enforces, in order:

```bash
task ci
# Equivalent to:
# cargo check --workspace --all-features --all-targets
# cargo clippy --workspace --all-features --all-targets -- -D warnings
# cargo fmt --all -- --check  (nightly rustfmt)
# cargo test --workspace --all-features
# cargo doc --workspace --all-features --no-deps
# cargo check -p connectrpc --no-default-features   (minimal build check)
# cargo test -p connectrpc --no-default-features
# ./examples/multiservice/test.sh
```

CI adds three jobs that `task ci` omits: the MSRV check (Rust 1.88), the wasm build (`wasm32-unknown-unknown`), and the conformance suite. Run `task conformance:test` separately for full protocol validation.

All CI jobs pin `protoc` at version 33.5 (required for `edition = "2023"` proto files) via `arduino/setup-protoc`. The `RUSTFLAGS=-Dwarnings` env var turns every compiler warning into a build error in both Check and Clippy.

Sources: [Taskfile.yaml:69-83](), [.github/workflows/ci.yml:1-178]()

---

## Summary Table: Examples at a Glance

| Example | What it adds | Run command |
|---|---|---|
| `streaming-tour` | All four RPC types | `task example:eliza` (see server/client bins) |
| `multiservice` | Multi-service, multi-package, WKTs | `task example:multiservice:test` |
| `eliza` | TLS, mTLS, CORS, bidi streaming | `task example:eliza` |
| `wasm-client` | Browser fetch transport | `./examples/wasm-client/test.sh` |
| `middleware` | Tower layer composition, auth | run manually (no task shortcut) |
| `mtls-identity` | mTLS + cert-SAN ACL | run manually |
| `bazel` | Bazel build integration | run manually |

Sources: [docs/guide.md:1264-1274](), [Taskfile.yaml:114-143]()

---

The most productive 30 minutes from here is: `task conformance:test` to see the protocol coverage, then `streaming-tour` to cement the handler shape for all four RPC types, then the TLS and deadline sections of `docs/guide.md` to understand what the defaults leave open. The CHANGELOG for 0.6.0 is the authoritative record of the interceptor design decisions and is worth reading as a companion to the guide.

Sources: [CHANGELOG.md:11-159]()
