# Implementation and scheduler coordination
Updated 2026-10-30. The initial semantic core, pull bodies, native HTTP/1 client/server codec, pooled application client and application server are implemented. Generic singleplex and multiplex pools are released as [base-pool 0.3.0](https://crates.io/crates/base-pool/0.3.0), with an experimental API. The HTTP packages remain unpublished prototypes. HTTP/1 TLS and shared protocol selection are implemented; compression, QUIC, HTTP/2/3 and managed high-level client tunnels remain planned. See [transport layering](transport.md).
## Package boundaries
- `base-http` owns generic requests/responses, scalar types, ordered `Fields`, body traits and in-memory bodies.
- `base-http1` owns HTTP/1 parsing, framing, read-ahead, interim transmission, persistence and managed protocol switches.
- `base-http-server` owns generic application handlers, accepted sockets, bounded listener admission, cancellation, joining and closure.
- [base-http-client](../crates/client/readme.md) owns endpoint-scoped calls, supervised upload/download, capacity-one multiplex admission and HTTP/1 retirement.
- [base-pool](https://github.com/base-rs/base-pool/blob/main/readme.md) owns distinct exclusive leases and shared-use reservations, bounded construction, FIFO acquisition and explicit retirement. It has no HTTP dependency.
The packages are independent siblings with explicit local path dependencies for unpublished HTTP crates. Scheduler dependencies use `base-scheduler 0.3` and require Rust 1.87. Precise lifetime captures allow connections to move their sockets and concrete directional handles together without self references. No parent Cargo workspace is introduced over unrelated projects. `base-http1` parses request heads, response heads and trailers with its own parser (`design/http1-parser.md`). The server uses `FuturesUnordered` for supervised connection futures; it does not spawn or detach tasks. No `http`, `http-body`, Hyper or Tokio runtime is a production dependency.
Orchestration follows [clients, connections and protocol negotiation](client-connection.md): `Client` is the durable remote-server interface; `Connection` is an established transport with its selected HTTP session. The initial engine is HTTP/1 over resolved plain TCP or rustls TLS. The client uses the released multiplex pool with capacity one per HTTP/1 connection; ALPN is supplied and interpreted by the shared protocol layer; additional HTTP engines remain absent. Low-level HTTP/1 callers can still supply connected lanes and own progress, reuse and closure themselves.
## Semantic core
`Request<B>` and `Response<B>` carry public `metadata` (`RequestMetadata` / `ResponseMetadata`) and arbitrary body owners. Dereferencing exposes metadata members. `map_body`, `with_body`, `into_parts` and `from_parts` preserve ownership without cloning or erasing the body type. `Method`, `Status`, `Version` and `Protocol` retain the previously reviewed semantics. `101` is informational but final, and every successful 2xx CONNECT establishes a tunnel.
`Fields` stores raw ordered `pairs` with token names and opaque values, without policy, cached interpretation or trailer markers. `HttpHeaders::new(fields)` owns explicit HTTP interpretation: a lazy semantic index, configurable policy and provenance for explicitly appended trailers. Names retain spelling and values remain opaque bytes. `append_trailers` is transactional when validation fails; `into_parts` recovers initial/trailing sections. The interpreter combines comma/cookie values, preserves separate cookies/challenges, rejects duplicate singletons and applies trailer policy. Field-specific date/range/authentication/negotiation parsers remain follow-up work.
HTTP/1 validates and extracts control fields before exposing application fields. Content-Length becomes typed `content_length` (including HEAD/304), Host becomes authority, transfer coding stays in framing, and Connection/Upgrade decisions stay with the exchange. Connection-nominated fields are removed regardless of order, including from body trailer frames. Their names remain private exchange state until input completes. Outgoing `persistent` is a reuse preference; incoming hop policy is not forwarded through metadata. Raw outgoing controls remain accepted with strict framing and typed/raw consistency validation; generated fields never mutate metadata. Field values are opaque in the semantic model. HTTP/1 rejects sent values containing CR, LF or NUL before writing a head or trailer section, using the flag `Field` records at construction, because those bytes change how the text is framed or interpreted. Received values follow the RFC 9110 field-value grammar: the HTTP/1 parser checks request, response and trailer values.
Short names and values (up to 24 bytes each) and the first pair have inline storage, with heap fallback for longer data and larger collections. The index and custom policy tables live exclusively in `HttpHeaders`, outside ordinary raw messages, and allocate only when used. Construction accepts `AsRef<str>` names so borrowed names need no temporary `String`. This prototype bound replaces `Into<String>`; custom name types must implement `AsRef<str>`. Public accessors still return `&str`, byte slices and an ordered field slice, and cloned fields remain independently mutable.
Request targets use `Scheme`, `Authority` and `Path` rather than `String`. Schemes and authorities are case-insensitive, so they are stored in their canonical lowercase form: `http` and `https` in any case become borrowed constants, other schemes are lowercased, and authority conversions lowercase ASCII letters. Comparisons with `str` use that lowercase text. The client's endpoint check therefore accepts `HTTP` for a plain endpoint. Authorities and paths up to 24 bytes are stored inline whether they are supplied as `&str` or `String`, with heap fallback for longer values. Comparison, ordering, hashing and formatting use the text regardless of storage. All three dereference to `str` and convert from the common string types; paths are still preserved as supplied. The hello codec allocation probe falls from 2 allocations and 15 bytes to none per request, and the server no longer allocates a scheme for each request. Request metadata grows from 224 to 240 bytes on aarch64. The [request target report](../benchmarks/hello/request-target.md) records Linux instructions per request against Hyper/Tokio.
## Body interfaces
`ReadableBody` now combines asynchronous cancellation-aware owned frame reads with readiness, exhaustion, total representation length, replay, closure and trailer completion. `WritableBody` describes frame writes, convenience data writes, flushing, write closure and abandonment. Concrete `BodyReader<D>` and `BodyWriter<D>` implement those traits. `BodyWriter` also has inherent producer methods. `BodyReader::bounded(max_bytes, max_chunks)` returns the pair; `with_length` additionally declares representation length.
`EmptyBody`, `BufferedBody<D>` and `HeadBody` retain their earlier behavior. `BufferedBody` stores zero or one chunk inline and uses a vector for multiple chunks; its slice view, independent replay cursor and append-after-close behavior are unchanged. Total representation length survives reads and close; a HEAD body may emit no data while retaining a nonzero length. Empty chunks are data. Queue limits include empty chunks; oversized chunks fail immediately. Normal writer closure preserves queued chunks through EOF. Failure discards queued chunks and preserves its cause. Dropped endpoints wake blocked peers. Pre-cancellation and dropped pending writes do not mutate the queue. Notifications and arbitrary chunk destruction occur outside the mutex.
There is one pull abstraction. The earlier `BodyStream`, `StreamableBody`, `StreamBody`, `ReadBody` and `WriteBody` interfaces are removed. A driver runs body reads within its existing future. A custom body can consume incoming input while yielding outgoing data. Imperative producers use explicitly supervised concurrent work with the bounded queue. Application chunks need not implement unsafe completion-I/O buffer contracts.
`BodyFrame<D>` has `Data { data, end }`, `Headers { fields, end }`, and `End` variants with a uniform `is_end()` predicate. `D` is the body's existing data owner; neither cloning nor a default empty value is required. Initial fields remain in message metadata, while body headers own received trailers. `Request::read` and `Response::read` return frames without updating that metadata. Callers may forward them through `WritableBody::write_frame` or explicitly collect the fields through `HttpHeaders::append_trailers`, which moves pairs and applies the destination policy. The old `ReadableBody::finish` hook is removed. The HTTP/1 decoder marks final fixed-length data and returns chunked trailers as a terminal header frame. The writer honors final flags without an extra read, validates trailer order/eligibility, and serializes trailing frames without merging them into metadata. `ReadableBody::may_have_trailers()` defaults to true; static bodies return false. HTTP/1.1 uses the hint before body reads to choose chunked framing even for known lengths, which remain validated. HTTP/1.0 streams still use fixed or close-delimited output; actual trailers are rejected.
The bounded queue carries owned frames, accounts for data bytes and header name/value bytes, and counts every frame toward its item limit. It rejects data/repeated headers after a header frame, accepts separately observed `End`, and closes atomically when a terminal frame is accepted. Normal `close_write(None)` preserves queued frames and marks the last payload as final when read. Cancelled or dropped pending terminal writes neither enqueue their frame nor close output. Queue-capacity release does not define future multiplex protocol credit release. No HTTP/2 or HTTP/3 runtime implementation is introduced by this semantic change.
## HTTP/1 behavior
`RequestReader<R>` owns the input lane and read-ahead. It validates configured limits, strict CRLF, targets and Host, rejects ambiguous Content-Length/Transfer-Encoding, and supports fixed/chunked request bodies. Duplicate Content-Length is rejected even when values match. Only the chunked transfer coding is implemented. Chunk extensions are syntax-checked and ignored. Trailer fields use the semantic split and eligibility checks. Failed or abandoned started reads poison reuse; pre-cancellation preserves input.
`IncomingBody<'a, R>` borrows the input lane. `Exchange` copies immutable framing facts: HTTP version, HEAD/CONNECT classification, persistence and Expect/Continue. Ordinary server input with fixed zero length needs no shared state: it is already complete and cannot become a tunnel. Nonempty/chunked input, CONNECT and offered upgrades retain completion/switch/abandonment state in one `Arc`, including copied upgrade offers when present. Client response input always retains shared state independently of the matching request's body length, but no upgrade offers, since the client selects upgrades from its own request. The application's path, authority and ordinary fields have a single owner in its independently mutable request head. Mutating that head cannot change established framing or authorize an unoffered upgrade. The former `Exchange::head()` accessor is replaced by framing accessors such as `version()`, `is_head()` and `is_connect()`. `ResponseWriter<W>` owns the independent output lane, retries short writes, rejects zero progress, checks fixed lengths, and emits fixed, chunked or HTTP/1.0 close-delimited data. HEAD, 204, 304 and 205 rules receive explicit handling. Known trailers select chunked framing; generated framing fields stay in the wire output and body trailers remain separate. Trailer eligibility is checked independently of application policy overrides. Ordered repeated fields are preserved.
`InterimResponses<InterimWriter<W>>` transmits and flushes explicit 100/102/103 heads before the final response. A failed/abandoned started wire write poisons output. HTTP/1.0 interim responses are rejected. When an upgrade request also expects 100-continue, the writer ensures 100 precedes 101. Raw Upgrade fields can also advertise support in ordinary responses. Incoming requests are handled sequentially; coalesced/pipelined requests remain buffered for subsequent admission.
`Exchange` distinguishes completed, active and abandoned input. A final response can begin while its request body remains active; the output persistence result is eligibility, not proof of connection synchronization. Admission waits for completed input/output and joined concurrent work. Dropping or closing unfinished input disables reuse; no unread upload is silently drained. Streaming echo can therefore reuse its connection after consumption finishes. A parse error receives best-effort 400 or 431 followed by closure. Application/late body errors close the connection; automatic application-error-to-500 handling is not implemented.
For 101 and successful CONNECT, the response handshake is flushed and incoming reads become raw bytes. Read-ahead is preserved, framing fields are prohibited, and future HTTP admission stops. A handler can return the incoming body for a statically dispatched duplex echo. The server retains cancellation, accounting, lane retirement and socket closure throughout. No raw socket is handed to the application. Independent producer/input task orchestration beyond the returned body remains an application responsibility; the server does not create a detached input task when response production finishes.
`RequestWriter<W>` and `ResponseReader<R>` expose native client lanes and reuse the common body encoder/decoder. The writer validates targets/Host before output, flushes the request head before pulling its body, frames fixed/chunked uploads and trailers, and closes the body on return. HTTP/1.0 requests cannot be close-delimited. The reader delivers interim heads, handles HEAD/bodyless responses, validates framing and reads fixed/chunked/close-delimited data. All 2xx CONNECT responses ignore framing fields as required by RFC 9112; 101 validates the selected offered token. These switch responses expose raw incoming bytes and permanently disable HTTP reuse. Coordinating managed bidirectional client switch output remains unimplemented.
No request queue, endpoint establishment, pool or Expect/Continue wait policy is hidden in these primitives. A caller supplies the matching request head, supervises both directions and owns socket retirement. The [duplex experiment](duplex.md) verifies this using a three-party proxy. The high-level client driver now retains upload work across `call` return. Its initial scoped body boundary requires bodies to live for the driver scope; forwarding a transient borrowed incoming body through a longer-lived client remains follow-up ownership work.
## Application and scheduler lifecycle
`Handler` is generic over incoming body and interim hook types. Its associated `Body<B>` selects the outgoing body type without boxing; echo uses `B`, static handlers use `BufferedBody`, and imperative producers use `BodyReader`. The associated response type depends on the incoming body, while the interim hook is borrowed only during `call`. This prevents a returned body from retaining the response writer needed to serialize it.
`serve_connection` consumes an accepted scheduler socket. It constructs independent bound reader/writer handles once, serves exchanges, awaits both uncancellable lane closures even after error or cancellation, drops the handles, then exclusively closes the socket. Cancelling interrupts handler and output-body futures even if they ignore the passed signal. Dropping the whole serving future is not a cleanup join; callers cancel cooperatively and await it.
`serve` consumes an already-bound listener, caps concurrently owned connection futures, retains a pending accept across other connection completions, stops acceptance on cancellation, cancels active exchanges, joins their retirement and closes the listener. It reports aggregate connection/request/error counts and isolates peer failures. This shutdown terminates in-flight work; timed graceful draining and deadlines remain future policy. The server requires no scheduler `Spawn`, `Clone` or static application handler.
The input path reads directly into initialized per-connection parser storage, retaining spare storage across reads and publishing only successful read counts. Head parsing borrows that storage; returned body chunks still own copies. Server request heads are parsed directly from the available bytes in one pass that validates line endings, the start line, field names and values, and records field spans inline for up to 16 fields. One `from_utf8` over the whole head then provides the target and names as text. After an incomplete parse, the reader resumes a terminator search with three bytes of overlap and parses again only when the terminator arrives. Response heads and trailer sections use the same parser and reader, converting their recorded spans directly into reserved `Fields`. The exact configured field limit applies to each. Common method tokens use borrowed constants; extension methods retain their original spelling. Request field construction also records Host, framing, connection, Expect and upgrade facts, preserving ordinary field order and duplicates while excluding processed controls. A table-driven fast path accepts the common `reg-name[:port]` authority; other forms and invalid values use the full validator and its errors. Response planning collects framing/connection facts in one pass, borrows the semantic head and describes generated wire fields separately, avoiding a cloned field collection, temporary decimal strings and trailer-name joins. Generated fields do not mutate the application's head. Messages with several independent defects can report a different first validation error after fusing scans; acceptance and rejection rules are preserved.
Output batches headers, framing bytes and immediately available small chunks into a reusable buffer capped at 16 KiB. Large slices bypass the copy buffer. Status lines, fields and trailers serialize incrementally, so a large head or trailer section does not enlarge the retained output buffer. Ordinary static response headers and body share a transport write. The body readiness predicate remains a hint: before awaiting an actually suspended read, the driver flushes pending output and then awaits the same started read future. This includes incoming bodies with partial chunk framing. Interim responses, tunnel handshakes and client request heads flush explicitly before further input/production. A failed or abandoned write leaves output poisoned; closure discards pending bytes without retrying them.
Handler calls and outgoing body reads are pinned at construction; cancellation wrappers borrow those futures instead of owning and moving them. The handler future's scope ends before interim finalization, and each body-read future's scope ends before encoding its returned chunk. A suspended body read stays pinned across the output flush and resumed wait. This preserves custom future destruction timing as well as cancellation and partial-input state; it introduces no boxing or task.
These changes preserve generic bodies and the independent input/output futures. Output still uses the scheduler's borrowed scalar writes. Owned submissions, frozen receive leases and vectored output remain measured optimization opportunities. Backend closure follows the released [I/O guide](https://github.com/base-rs/base-scheduler/blob/main/context/io.md).
The [cost-reduction report](../benchmarks/hello/hyper-target.md) records the compact exchange, borrowed wire plan, inline fields and native readiness changes with preserved source/binary evidence and Hyper controls.
The subsequent [hot-path report](../benchmarks/hello/http-hot-path.md) records retained writer registration, fused protocol facts, conditional shared exchange state and common response encoding, including allocation/CPU evidence and throughput limitations.
Handler and body waits skip cancellation-listener work for `Cancellation::never()`. Cancellable handlers and bodies still have their futures raced against the signal, including implementations that ignore the supplied signal themselves.
## Validation and remaining scope
Semantic tests cover field policies, body mapping, readiness/length/replay, queue backpressure/cancellation/abandonment and interim sequencing. The bound-file-reader test retains separate asynchronous resource retirement.
HTTP/1 deterministic tests vary fragmentation, force one-byte output writes, check zero writes, short/long bodies, output poisoning, target forms and static trailers. Real TCP tests use the local Direct scheduler and an independent raw peer. They cover fixed/chunked streaming, persistent/pipelined requests, trailer delivery, interim-before-upload, bodyless responses, close-delimited HTTP/1.0, bounded producers, malformed framing, truncation, cancellation, listener limits and managed duplex upgrades/CONNECT with coalesced input and half closure. A runnable server example additionally supports independent tooling such as curl. Batching regressions cover one-write static responses, an inaccurate readiness hint without restarting the read, bounded batches before body completion, large fields/chunks/trailers, write failure without replay on close, parser limits on both sides of the stack threshold, and streaming echo stalled on a partial chunk delimiter. Client codec tests cover fragmentation, interim ordering, response trailers, bodyless responses, close-delimited input, invalid framing, truncation, abandonment, cancellation and switch classification. Three-party proxy tests demonstrate streaming, interim/trailer forwarding, response completion before upload production, 8 MiB transfers with delayed response consumption, reuse of both connections, and cancellation/error cleanup of all four lanes.
The API remains a prototype. Native client framing and scoped high-level client/pool orchestration exist; managed client tunnel coordination is not implemented. The proxy experiment explicitly owns and joins upload/download futures; it is not a drop-in high-level proxy handler. Broader fuzzing/interoperability, upgrade protocol/version representation, deadlines, TLS and alternative runtime backends remain follow-up work. The current semantic `Protocol` stores one token; raw upgrade offers are retained privately by the exchange, while selection is limited to one token. This is not a complete upgrade negotiation framework or full RFC conformance claim. Linux io_uring behavior and performance have not been measured on this macOS host.
The [future-ownership investigation](../benchmarks/hello/remaining-gap.md) records callsite pinning, unchanged body semantics, benchmark ownership alignment and remaining performance limits.
The [static hello comparison](../benchmarks/hello/results.md) adds a release-build performance baseline on macOS for native readiness, Tokio and Direct backends against Hyper/Tokio. This covers one application worker, persistent connections and a 13-byte response. The [integrated optimization comparison](../benchmarks/hello/http-optimization.md) records the production batching/allocation changes against a saved unchanged-HTTP binary using the same local scheduler. The [harness](../benchmarks/hello/readme.md) records settings and raw samples; it does not establish full alternative-backend conformance or Linux io_uring throughput.
Fields, messages and body semantics follow `protocol-http` revision `dcbc0fe7ad6b9a55986f003d25c0c0023228db0b`; MIT attribution remains preserved. The [source review](source-review.md) retains historical scheduler and Ruby implementation evidence. The simplified pull-body design is now exercised by an actual server path, with further production validation still required.
### Owned frame validation
The frame migration passes the `base-http`, `base-http1`, and `base-http-server` suites, including end-to-end duplex proxying, fragmented trailers and connection reuse. New cases cover final-data completion without another read, separate completion after headers, terminal-write cancellation, frame ordering, header byte/item limits, non-Clone/non-Default data owners, and forwarding without copying heap-backed data or field values. Clippy and Rust 1.85.1 all-target checks pass.
The hello-world release allocation probe, compared against the pre-migration source snapshot over 10,000 requests, remains at 2 allocations and 15 allocated bytes per request, with one transport write per response. On this aarch64 build, `IncomingBody` shrinks from 176 to 64 bytes because it no longer stores trailers, while the generic response-write future grows from 976 to 1,248 bytes as it retains frame and trailer state. These are allocation/layout observations, not a throughput comparison.
### Metadata and header interpretation separation
The 2026-10-28 refactor removes trailer markers, policy and semantic indexing from raw `Fields`. `RequestMetadata` / `ResponseMetadata` and `.metadata` replace the former head names. `HttpHeaders` owns optional interpreted lookup, custom policy and explicit trailer accumulation, with `into_parts` retaining section provenance. Body frames remain the sole source of trailers; message reads do not merge them.
HTTP/1 extracts typed Content-Length and authority before delivering application fields. Connection and transfer controls remain in protocol state. Private exchange state retains Connection-nominated names so both initial and trailing sections are filtered before forwarding, without affecting the next exchange. Known-length bodies can produce trailers using `may_have_trailers`; chunked output still validates their byte count. Static bodies exclude trailers. HTTP/1.0 keeps its fixed/close-delimited behavior and rejects actual trailer frames. Raw outgoing controls remain supported with framing and typed/raw conflict validation.
Validation passes: 63 semantic tests, 55 HTTP/1 codec tests, 27 server/proxy tests and five doctests; all three packages pass formatting, Clippy with warnings denied, and Rust 1.85.1 all-target checks. The hello benchmark compiles across its targets, and the single-file client/server example completes a real loopback exchange and cleanup. The existing single-token upgrade representation and managed-client-tunnel limitations remain as described above.
The release allocation probe still reports 2 allocations and 15 allocated bytes per request over 10,000 requests, with one transport write per response, matching the pre-refactor baseline. On this aarch64 build, raw `Fields` is 80 bytes, request metadata is 224 bytes, `IncomingBody` remains 64 bytes, and the response-write future is 1,136 bytes (previous frame-migration observation: 1,248 bytes). These layout/allocation checks do not establish a throughput improvement.
## Pooled client implementation
`base-http-client::Client::new` returns a cloneable endpoint handle and an explicit `Driver`. The scheduler constructs I/O capabilities; polling the driver supervises dialing, queued call assignments/upload/download, TLS progress and cleanup. No task is detached. A connection holds its socket and concrete reader/writer codecs together under the corrected scheduler factory contract. Direct HTTP/1 `open`/write/read takes the retained codecs in the calling task and returns them synchronously before releasing pool admission. There is no per-request driver submission or codec-recovery channel on that path. A regression establishes one plaintext connection, stops polling the driver, and completes four further requests on it. Queued `call`, TLS control output, synchronous deferred `close_write` and asynchronous retirement still require supervised progress. TLS reads, writes and flushes now directly poll retained I/O futures; input processes incoming control records as it reads.
Each connection publishes `Admission::concurrent(1)` through `base-pool::MultiplexPool`. The active use remains charged until upload completes and the application consumes the response's final frame. Network read-ahead alone does not release admission. Abandoning a partial response cancels the exchange and retires HTTP/1 without draining unread bytes; retirement capacity remains charged through lane cleanup and socket closure. Graceful closure finishes accepted calls; driver lifetime cancellation terminates them and joins cleanup. Driver drop is synchronous abandonment, without an async join.
One boxed producer stream per call accepts heterogeneous bodies, with dynamic polling per frame and no boxed future allocation per body read. Vectors move; borrowed slices are copied into queued vectors. Transport dispatch remains static. Request bodies and interim observers may borrow for the driver's scope, which permits borrowed application data but does not yet accept shorter-lived server input through a long-lived client. This boundary is an explicit client ownership decision, not a consequence of a temporary socket borrow.
TCP integration tests cover borrowed POST then GET reuse, early final response with delayed upload, full response-queue backpressure with independent 1 MiB upload, interim responses and trailers, partial-response abandonment and replacement, call cancellation/drop, cancelled pool waiters, construction-failure cleanup, known-empty response reuse, request limits, rejected-body closure, uncooperative upload shutdown and driver abandonment. The single-file pooled client/server example uses the actual application server and sends two echoed POSTs over one session. Managed CONNECT/upgrades, DNS integration, retries and additional protocols remain outside this first path.
The registry-backed dependency graph uses `base-scheduler 0.3.0`, `base-pool 0.3.0` and `base-endpoint 0.1.0`, without local overrides. With them, `cargo test --locked --workspace --all-features` passes on macOS with Rust 1.87 and on Linux (hana) with Rust 1.99.1, and the pooled client/server example runs on Linux. With the earlier 0.2.0 releases, Rust 1.87 passed 63 semantic, 55 codec, 27 server/proxy and 13 client integration tests on macOS. The 13 client tests and pooled client/server example also passed on Linux via `aiko.local`.
## HTTP/1 HTTPS integration
`base-http-protocol` owns enabled engine policy and client/server codec factories, following Async::HTTP. `base-http1` consumes plaintext stream capabilities and has no TLS or ALPN logic. `base-endpoint` uses scheduler 0.3 and adds supervised rustls duplex progress over persistent directional handles. The client accepts explicit TLS configuration and identity with `Endpoint::with_tls`; the server exposes `serve_tls` and `serve_tls_connection`.
Five HTTPS tests cover TLS 1.2/1.3 echo/reuse, stalled-handshake acceptance, missing-ALPN policy before pool publication, response backpressure with independent upload, an independent HTTP client and identity-verification failure. Three endpoint duplex regressions cover independent blocking-peer echo, lifetime cancellation and truncation. Existing plain HTTP framing and lifecycle tests pass. The pooled single-file example supports `--features rustls -- --https`. These are correctness checks, without a TLS performance claim.
## TLS client progress and future layout
The client performance investigation keeps directional I/O state in retained futures. Encrypted reads and writes poll the connection's pinned I/O futures immediately rather than notifying a supervisor and awaiting progress on a second turn. A stable waker routes blocked output to both foreground and supervised pollers. The directional futures retain partial ciphertext, ticket accounting and their scheduler handles; shutdown joins them before closing the socket. Initialized output storage is reused between records instead of clearing and zeroing 16 KiB on every small write.
The response parser's scratch header array now lives in a synchronous helper. It does not span an asynchronous interim-response callback: the measured response-read future shrank from 4,064 to 800 bytes on Rust 1.87/aarch64. Protocol validation and header limits remain unchanged. A print-only layout probe is retained with the experiment results, rather than in the normal integration suite.
Endpoint regressions verify a 256 KiB echo in both directions with supervision frozen for TLS 1.2/1.3, distinct foreground/background wake routing and release of completed wake registrations. The existing HTTP/TLS duplex, proxy, cancellation, truncation and cleanup suites remain applicable. See the [progress investigation](../benchmarks/client/tls-progress-20261028.md) for interleaved measurements and rejected experiments. No scheduler or pool changes are required by the retained implementation.
## Direct client exchange bookkeeping
`PendingResponse` now polls the retained response stream for metadata and transfers that same stream into `StreamingResponseBody`, removing a boxed wrapper future. One exchange completion record owns output closure mode, its finalization result/waker and admission accounting. Direction guards keep ordinary completion mode inline, removing separate flag allocations and the unconditional completion channel. A dropped or cancelled completion wait unregisters its waker without consuming the result. Each exchange retains its own identity; records are not reset while old handles exist.
Both codec directions must return before admission is released. Lifecycle notifications and completion wakes occur outside the completion-state mutex. Socket reads and cancellable interim callbacks already observe the local exchange signal, while a separate wrapper retains caller-lifetime cancellation. Regressions cover deferred closure success/failure, dropped/cancelled completion waits, cancellation during a blocked interim callback and old completed handles during a subsequent request. The [allocation investigation](../benchmarks/client/exchange-overhead-20261029.md) records counts and throughput separately.
## Direct operation storage reuse
The direct client now reuses the pinned allocations for its opening future, output sink and input stream. Each connection retains at most one idle allocation of each concrete type. Taking storage precedes construction of the next operation; dropping its owning handle clears all captures before caching it. `pin-project-lite` supplies in-place pinned replacement, and a pointer-sized `crossbeam-utils::AtomicCell` transfers empty storage. The client continues to forbid handwritten unsafe code. The factory exists once per connection; there is no new HTTP data queue, worker handoff or future allocation per body frame.
The exchange completion/cancellation record remains unique to each request. Known-empty input releases its adapter immediately; terminal nonempty input releases it after the final frame. Regression coverage includes pinned address stability, in-place destruction, unpolled abandonment, concurrent returns, retained completed handles and immediate observer release. The [storage reuse report](../benchmarks/client/exchange-reuse-20261029.md) distinguishes allocation diagnostics from ordinary timing and records the idle-memory and cold-connection tradeoff. That report was measured with scheduler and pool 0.2.0; the workspace now uses 0.3.0.