1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
//! plecto-server — the M2 fast path (ADR 000013, TLS 000014, HTTP/2 000015, HTTP/3 000016).
//!
//! A tokio listener that turns Plecto from a library into an actual reverse proxy. It serves
//! HTTP/1.1 and HTTP/2 over TCP (hyper, ALPN-negotiated — ADR 000015) and HTTP/3 over QUIC (quinn +
//! the h3 crate, an independent UDP listener advertised via `Alt-Svc` — ADR 000016). All three
//! transports feed the same transaction core (`proxy_core`); only the body adapters differ.
//! Per request it: builds a header-only `HttpRequest`, asks the control plane which route
//! matches (host + path prefix), runs that route's filter chain, and either responds now (a
//! filter short-circuited / failed closed) or forwards the request to the route's upstream and
//! runs the response side of the chain on the way back.
//!
//! **sync↔async bridge (the §6.3 prerequisite).** Filter execution is synchronous and runs on a
//! wasmtime `Store` that is `!Send`, so it cannot cross an `.await`. Each chain dispatch is moved
//! to tokio's blocking pool via `spawn_blocking`; the M1 trusted instance pool handles instance
//! reuse and saturation there. Route matching is pure config lookup and stays on the async thread.
//!
//! **Request body: buffered ONLY when a filter reads it (ADR 000025 / 000038).** A route whose
//! filters all target the header-only `filter` world streams the request body straight to the
//! upstream (zero-copy); a route with a filter that exports `on-request-body` (`reads_body`) has the
//! body buffered (bounded) and run through the `on-request-body` chain. The response body always
//! streams straight back — filters see response headers / status only (they may synthesise a
//! short-circuit body of their own).
// Hot-path discipline (bp-rust): no unwrap/expect/panic/indexing on the data plane. Exempted
// under `cfg(test)` — this crate's own `#[cfg(test)] mod` blocks legitimately use them;
// `tests/*.rs` integration tests are separate crates and are never subject to this attribute.
// `pub`: the out-of-workspace fuzz harness (`fuzz/`) drives the pure parser (ADR 000057). Not a
// semver surface — the crate is `publish = false`.
use Arc;
use Bytes;
use BoxBody;
use HeaderValue;
use HttpConnector;
use Control;
use Semaphore;
use crateServerMetrics;
use crateUpstreamClients;
pub use ;
/// Cap glibc's per-thread malloc arenas at process start to bound RSS on many-core hosts.
///
/// glibc defaults to `8 × ncpu` arenas on 64-bit. Under a many-threaded proxy doing bursty
/// per-request allocations, freed memory lingers in each thread's arena instead of returning to the
/// OS, inflating RSS (measured ~2.5× at 1 MB bodies × 50 conns — docs/servey body-tax). This is a
/// defensive complement to the real fix (not buffering a body no filter reads); routes that
/// legitimately buffer still allocate, and this bounds their arena fragmentation.
///
/// `M_ARENA_MAX` only gates creation of NEW arenas and never reclaims existing ones, so this MUST
/// run before the runtime spawns its worker threads (call it first in `main`). Default cap **4** — a
/// portable, contention-safe value used across multithreaded services, chosen over the value that
/// minimised RSS on one host (1) precisely because Plecto is self-hosted on varied machines.
/// Override with `PLECTO_MALLOC_ARENA_MAX` (`0` leaves glibc's default in place). No-op off glibc.
/// A boxed, `Send` error — the unified error type for the boxed request/response bodies.
pub type BoxError = ;
/// The response body the service yields: either a synthesised buffer (`Full`, for a short-circuit
/// or a fail-closed 5xx) or the upstream's streamed body (`Incoming`), unified behind one boxed
/// type so the service has a single return shape.
pub type ResponseBody = ;
/// The request body forwarded to the upstream, boxed so one type covers every inbound transport:
/// the hyper `Incoming` (HTTP/1.1 + HTTP/2) and the QUIC/h3 recv stream (HTTP/3, ADR 000016). The
/// body streams straight through opaquely (header-only contract, ADR 000010) regardless of source.
pub type ReqBody = ;
/// Global cap on concurrently-served connections across all transports (CWE-770). A permit
/// is acquired BEFORE each accept, so at saturation the listener stops pulling new connections off
/// the OS backlog (natural backpressure) instead of spawning per-connection tasks unboundedly.
pub const MAX_CONNECTIONS: usize = 10_000;
/// Per-source-IP cap on concurrently-served connections (CWE-770/CWE-400, docs/servey production
/// hardening — ADR 000027 amendment), enforced by [`conn_limit::PerIpConnLimit`] in both accept
/// loops (TCP `listener.rs`, QUIC `h3/endpoint.rs`). `MAX_CONNECTIONS` alone bounds the total but
/// not its distribution: without this, one source can hold every permit and starve every other
/// client. ~2.6% of `MAX_CONNECTIONS` — comfortably above any legitimate single-source workload
/// (even a large NAT/corporate gateway), while an attacker needs ~40 distinct source addresses
/// (or hash-colliding /64s, for IPv6) to exhaust the pool alone, instead of one.
pub const MAX_CONNECTIONS_PER_IP: u32 = 256;
/// Raise the process's soft `RLIMIT_NOFILE` to match the hard limit at startup (Unix only; a no-op
/// twin exists for other platforms below).
///
/// Most distros ship a low default soft limit (often 1024) while the hard limit is already
/// generous — an unprivileged process may always raise its own soft limit up to the hard limit
/// (POSIX `setrlimit(2)`, no `CAP_SYS_RESOURCE` needed). Without this, an accept loop asking for
/// `MAX_CONNECTIONS` sockets hits EMFILE at the OS default long before its own cap — and, by
/// design, a transient accept error is logged and the loop continues (`listener.rs`), so the
/// server silently stops admitting new connections instead of crashing loudly. Raising the HARD
/// limit itself needs a privilege Plecto does not request (self-hosting simplicity, deny-by-default
/// P4); if the hard limit is ALSO below what `MAX_CONNECTIONS` needs, this only warns — an
/// operator must raise it externally (systemd `LimitNOFILE=`, `docker run --ulimit nofile=...`, or
/// the container runtime's own ulimit configuration).
/// No POSIX resource limits on this platform — nothing to raise.
/// Per-connection cap on concurrent HTTP/2 streams (ADR 000015). A fixed, conservative bound (not
/// yet manifest-configurable): it stops a single h2 connection from monopolising the fixed-capacity
/// M1 instance pool (ADR 000012) with concurrent chain dispatches, and is defence-in-depth against
/// stream-flooding DoS (the h2 crate already mitigates Rapid Reset, CVE-2023-44487). 100 is the
/// RFC 9113 recommended floor; hyper's own default is version-dependent and not API-stable, so we
/// pin it explicitly.
pub const MAX_CONCURRENT_STREAMS: u32 = 100;
/// Shared per-server state: the control plane (filters, routes, reload), the upstream clients, and
/// the `Alt-Svc` header value advertising HTTP/3 (ADR 000016) — `Some` only when a QUIC listener is
/// bound, and added to TCP (HTTP/1.1 + HTTP/2) responses to steer capable clients to h3.
pub
/// An upstream TCP connector with `TCP_NODELAY` set. A proxy must disable Nagle on its upstream
/// sockets: with Nagle on, a streamed request body sent in several writes stalls ~40 ms on the
/// peer's delayed-ACK timer (surfaced by the body benchmark as a p99 cliff on large streamed
/// bodies). Disabling Nagle on proxy/upstream sockets is standard practice across mature L7 proxies.
/// Both the forwarding clients and the health prober use it — plain, and wrapped by the rustls
/// connector for a TLS upstream (ADR 000042), which is why `enforce_http` is off (the wrapping
/// `HttpsConnector` dials `https://` URIs through it).
pub