Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .vitepress/config/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ const guideSidebar: DefaultTheme.SidebarItem[] = [
collapsed: true,
items: [
{ text: 'Architecture Patterns', link: '/en/guide/production/architecture' },
{ text: 'Transport Matrix', link: '/en/guide/production/transport-matrix' },
{ text: 'Docker', link: '/en/guide/production/docker' },
{ text: 'Kubernetes', link: '/en/guide/production/kubernetes' },
{ text: 'Envoy Gateway', link: '/en/guide/production/envoy-gateway' },
Expand Down
80 changes: 80 additions & 0 deletions en/guide/production/transport-matrix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
---
outline: deep
---

# Transport Matrix

Which RPC types work on which server transport. The Connect protocol states:
**"Bidirectional streaming requires HTTP/2, but the other RPC types also
support HTTP/1.1"** — a bidi service on an HTTP/1.1 transport does not fail
at startup by itself; the first client send simply hangs forever (or the
client receives `HTTP 505`). Connectum turns this into a startup diagnostic —
see [Startup validation](#startup-validation) below.

## Server transport modes

`createServer()` picks the transport from `tls` and `allowHTTP1`:

| Configuration | Transport | Node server |
|---|---|---|
| no `tls`, `allowHTTP1: true` (**default**) | plaintext HTTP/1.1 | `http.createServer` |
| no `tls`, `allowHTTP1: false` | plaintext HTTP/2 (h2c) | `http2.createServer` |
| `tls` configured | TLS + ALPN (HTTP/2 and HTTP/1.1 negotiated) | `http2.createSecureServer` |

## RPC type support

| Transport | Unary | Server streaming | Client streaming | Bidi streaming |
|---|---|---|---|---|
| Plaintext HTTP/1.1 (default) | ✅ | ✅ | ✅ | ❌ blocked at startup |
| Plaintext h2c (`allowHTTP1: false`) | ✅ | ✅ | ✅ | ✅ |
| TLS + ALPN, HTTP/2 negotiated | ✅ | ✅ | ✅ | ✅ |
| TLS + ALPN, **HTTP/1.1 negotiated** | ✅ | ✅ | ✅ | ❌ hangs at runtime |

::: warning Residual risk: TLS with an HTTP/1.1 client
A TLS server with `allowHTTP1: true` is *streaming-capable* (HTTP/2 is
negotiable), so startup validation does not hard-fail — but a client or
intermediary that negotiates HTTP/1.1 over TLS (a client without `h2` in its
ALPN list, a proxy with an HTTP/1.1 upstream leg) hits the same silent hang on
bidi calls. When bidi methods are present on such a server, Connectum logs a
**one-time warning** at startup. Remove the risk entirely by setting
`allowHTTP1: false` (the server then refuses HTTP/1.1 at ALPN, so HTTP/1.1
clients fail the handshake explicitly instead of hanging on bidi), or keep bidi
clients on HTTP/2 transports (`createGrpcTransport`, or `createConnectTransport`
with `httpVersion: "2"`). Silence the warning with `transportValidation: "off"`.
:::

::: tip Pure gRPC protocol needs HTTP/2 even for unary
The matrix above is for the **Connect protocol**. The classic gRPC protocol
(used by `grpcurl`, gRPC reflection clients, and `createGrpcTransport`)
requires HTTP/2 for *every* RPC type — on the default plaintext HTTP/1.1
server, gRPC clients and `grpcurl` do not work at all. Use h2c or TLS.
:::

## Startup validation

When a registered service defines bidi-streaming methods and the effective
transport is plaintext HTTP/1.1, `server.start()` rejects with a
`TransportValidationError` carrying the stable code
`CONNECTUM_UNSUPPORTED_STREAMING_TRANSPORT`, the affected
`service.method` list, and both fixes:

```typescript
const server = createServer({
services: [bidiRoutes],
// no TLS + allowHTTP1 default → plaintext HTTP/1.1
});

await server.start();
// ✖ TransportValidationError [CONNECTUM_UNSUPPORTED_STREAMING_TRANSPORT]:
// - acme.v1.ScannerService.StreamCodes (bidi_streaming)
// Fix: allowHTTP1: false (h2c) or configure TLS.
```

Downgrade the check with `transportValidation: "warn"` (log once, start
anyway) or `"off"` — for example behind an HTTP/2-terminating proxy where the
bidi method is intentionally unused.

## Learn More

- [Security & TLS](/en/guide/security) — TLS configuration, mTLS
- [Server Configuration](/en/guide/server/configuration) — `createServer()` options
4 changes: 2 additions & 2 deletions en/guide/security.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const server = createServer({
await server.start();
```

When TLS is configured, Connectum creates an HTTP/2 secure server (`http2.createSecureServer`). Without TLS, it creates a plaintext HTTP/2 server.
When TLS is configured, Connectum creates an HTTP/2 secure server (`http2.createSecureServer`) with ALPN negotiation. Without TLS, the default server is **plaintext HTTP/1.1** (`http.createServer`, since `allowHTTP1` defaults to `true`); set `allowHTTP1: false` to get a plaintext HTTP/2 (h2c) server instead. See the [transport matrix](/en/guide/production/transport-matrix) for which RPC types each transport supports.

## Key Concepts

Expand All @@ -32,7 +32,7 @@ When TLS is configured, Connectum creates an HTTP/2 secure server (`http2.create
| **TLS Options** | `keyPath` + `certPath` for explicit paths, or `dirPath` for directory-based config |
| **Environment Variables** | `TLS_DIR_PATH`, `TLS_KEY_PATH`, `TLS_CERT_PATH` for deployment flexibility |
| **mTLS** | Mutual TLS via `http2Options`: `requestCert`, `rejectUnauthorized`, `ca` |
| **HTTP/2** | Default transport; `allowHTTP1: true` enables HTTP/1.1 fallback for ConnectRPC |
| **Transports** | Without TLS: HTTP/1.1 by default, h2c with `allowHTTP1: false`; with TLS: ALPN (HTTP/2 + HTTP/1.1). See the [transport matrix](/en/guide/production/transport-matrix) |
| **Utility Functions** | `readTLSCertificates()`, `getTLSPath()` from `@connectum/core` |

## Learn More
Expand Down
3 changes: 2 additions & 1 deletion en/packages/core.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ The server is created in `CREATED` state. Call `server.start()` to begin accepti
| `protocols` | `ProtocolRegistration[]` | `[]` | Protocol plugins (healthcheck, reflection, custom) |
| `shutdown` | `ShutdownOptions` | `{}` | Graceful shutdown configuration |
| `interceptors` | `Interceptor[]` | `[]` | ConnectRPC interceptors. When omitted or `[]`, no interceptors are applied. Use `createDefaultInterceptors()` from `@connectum/interceptors` for the production-ready chain. |
| `allowHTTP1` | `boolean` | `true` | Allow HTTP/1.1 connections |
| `allowHTTP1` | `boolean` | `true` | Allow HTTP/1.1 connections. Without TLS the default server is plaintext HTTP/1.1; set `false` for h2c. See the [transport matrix](/en/guide/production/transport-matrix) |
| `handshakeTimeout` | `number` | `30000` | Handshake timeout in milliseconds |
| `eventBus` | `EventBusLike` | `undefined` | Event bus for lifecycle management |
| `http2Options` | `SecureServerOptions` | `undefined` | Additional HTTP/2 server options |
| `transportValidation` | `"error" \| "warn" \| "off"` | `"error"` | Startup validation: bidi-streaming methods on a plaintext HTTP/1.1 transport fail fast with `CONNECTUM_UNSUPPORTED_STREAMING_TRANSPORT` instead of hanging at runtime. See the [transport matrix](/en/guide/production/transport-matrix) |

### `Server` Interface

Expand Down