Skip to content

feat(middleware): response-phase modules — request-id + header-transform - #6

Merged
luthermonson merged 2 commits into
mainfrom
feat/response-phase-modules
Aug 24, 2026
Merged

feat(middleware): response-phase modules — request-id + header-transform#6
luthermonson merged 2 commits into
mainfrom
feat/response-phase-modules

Conversation

@luthermonson

@luthermonson luthermonson commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Two native middleware modules built on the response-phase ABI from ephpm/ephpm #408, plus their cdylib shells and release wiring. One PR to keep the shared files (modules/src/lib.rs, release.yml all_modules, README.md) conflict-free.

ABI pin

Pins ephpm-middleware / ephpm-kv to e63284838d07d348e2155e76916daaf9782c012b — the #408 merge on main, which adds the ResponseMiddleware trait, the declare!(Type, response) arm, and the ResponseView accessors. Deliberately not advanced to #409's scheme/host/body accessors — neither module needs them.

Modules

Module Phases What it does
request-id request + response Generate or honor an inbound X-Request-Id, inject it for PHP, echo it on the response.
header-transform request + response Set request headers PHP sees; set/remove response headers out.

request-id

Request phase resolves the id (trusted inbound if trust_inbound and the value is a safe printable-ASCII token ≤200 bytes, else a generated UUIDv4), injects it as a request-header override for PHP, and stages it as a response header. The response phase fills the header in only when it is still absent (the static-file path runs no request phase) and is idempotent otherwise. The request phase must carry the id itself because the v1 response phase is handed a request view rebuilt from the original inbound headers and cannot see request-phase state — regenerating in the response phase would echo a different id than PHP logged. A trusted-but-malformed inbound id (CR/LF, oversized) is regenerated rather than reflected.

header-transform

request.set / response.set are replace-or-add; response.remove deletes. request.remove and any add are rejected at init rather than silently ignored: the v1 ABI request phase can only override a request header (not delete it), and neither phase has a duplicate-append primitive, so add would equal set. Honest to the ABI, no silent no-ops.

Why no compression module

An earlier revision of this PR included a compression response-phase module; it has been dropped. ePHPm core already compresses buffered responses by default — [server.response] compression is on by default and runs brotli-then-gzip over buffered PHP/static responses (Accept-Encoding negotiation, Vary, Content-Length) before the response phase runs. A middleware compressor would therefore be redundant and inert on a stock server (it would see an existing Content-Encoding and stand down), so shipping one adds surface for no gain. Response-body compression stays a core-server concern; the README documents this explicitly.

Tests / CI

cargo build --workspace (+ --release), cargo test --workspace (98 module unit tests incl. request-phase decisions and response-phase transforms driven through the real ResponseCtx/ResponseView), cargo clippy --workspace --all-targets -- -D warnings, and cargo +nightly fmt --all -- --check all clean locally. Two all_modules entries added to release.yml.

…rm, compression

Add three native middleware modules that use the response-phase ABI from
ephpm/ephpm #408 (pinned at main e6328483), plus the two cdylib shells and
release wiring for each.

- request-id (request + response phase): generate or honor an inbound
  X-Request-Id, inject it for PHP, and echo it on the response. Trusted inbound
  ids are validated (printable ASCII, bounded length) to block header
  injection. The request phase carries the id to the response itself because
  the v1 response phase cannot see request-phase state; the response phase
  fills the header in only when absent (e.g. the static-file path).
- header-transform (request + response phase): set request headers PHP sees;
  set/remove response headers out. Request-side remove and duplicate-append are
  rejected at init rather than silently ignored — the v1 ABI supports neither.
- compression (response phase only): gzip/brotli the buffered body with
  Accept-Encoding negotiation, Vary, host-recomputed Content-Length.

Compression overlap: ePHPm core already compresses buffered responses by
default (brotli-then-gzip, before the response phase). This module skips any
response that already carries a Content-Encoding, so it is inert on a stock
server and never double-encodes — mount it only when core compression is off.
The overlap is documented in the module docs and the README.

Bumps the ephpm-middleware / ephpm-kv git pin to e6328483 (the #408 merge) for
the ResponseMiddleware trait, declare!(Type, response) arm, and ResponseView
accessors. 114 module unit tests, clippy, and fmt all clean.
Response-body compression is redundant with ePHPm's built-in
`[server.response] compression`, which is on by default and already runs
brotli-then-gzip over buffered PHP/static responses (with Accept-Encoding
negotiation, Vary, and Content-Length) BEFORE the response phase. A middleware
compressor would therefore be redundant and inert on a stock server, so it is
not shipped.

Removes the compression impl, the ephpm-middleware-compression cdylib crate,
its modules/lib.rs pub mod, its release.yml all_modules entry, its README
table/layout rows, and the now-unused flate2/brotli workspace deps. request-id
and header-transform are unchanged.
@luthermonson luthermonson changed the title feat(middleware): response-phase modules — request-id, header-transform, compression feat(middleware): response-phase modules — request-id + header-transform Aug 24, 2026
@luthermonson
luthermonson merged commit 0732247 into main Aug 24, 2026
4 checks passed
@luthermonson
luthermonson deleted the feat/response-phase-modules branch August 24, 2026 01:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant