feat(middleware): response-phase modules — request-id + header-transform - #6
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.ymlall_modules,README.md) conflict-free.ABI pin
Pins
ephpm-middleware/ephpm-kvtoe63284838d07d348e2155e76916daaf9782c012b— the #408 merge onmain, which adds theResponseMiddlewaretrait, thedeclare!(Type, response)arm, and theResponseViewaccessors. Deliberately not advanced to #409's scheme/host/body accessors — neither module needs them.Modules
request-idX-Request-Id, inject it for PHP, echo it on the response.header-transformrequest-id
Request phase resolves the id (trusted inbound if
trust_inboundand 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.setare replace-or-add;response.removedeletes.request.removeand anyaddare 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, soaddwould equalset. Honest to the ABI, no silent no-ops.Why no compression module
An earlier revision of this PR included a
compressionresponse-phase module; it has been dropped. ePHPm core already compresses buffered responses by default —[server.response] compressionis 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 existingContent-Encodingand 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 realResponseCtx/ResponseView),cargo clippy --workspace --all-targets -- -D warnings, andcargo +nightly fmt --all -- --checkall clean locally. Twoall_modulesentries added torelease.yml.