Skip to content
Merged
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
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ jobs:
{"short":"redirect", "crate":"ephpm-middleware-redirect"},
{"short":"security-headers", "crate":"ephpm-middleware-security-headers"},
{"short":"maintenance-mode", "crate":"ephpm-middleware-maintenance-mode"},
{"short":"ip-allowlist", "crate":"ephpm-middleware-ip-allowlist"}
{"short":"ip-allowlist", "crate":"ephpm-middleware-ip-allowlist"},
{"short":"request-id", "crate":"ephpm-middleware-request-id"},
{"short":"header-transform", "crate":"ephpm-middleware-header-transform"}
]'

if [ "$ONLY_HOST" = "true" ]; then
Expand Down
22 changes: 19 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 8 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,20 @@ repository = "https://github.com/ephpm/middleware"
# by `rev` exactly like ePHPm pins litewire: a drift in `EphpmHostV1` / `ABI_V1`
# would be silent UB at the FFI boundary, so the module is provably built
# against one specific host-ABI commit. Bump = replace `rev` + `cargo update`.
#
# Pinned at ePHPm main `e63284838d07d348e2155e76916daaf9782c012b` — the merge of
# #408, which added the response-phase ABI hook (`ResponseMiddleware` /
# `declare!(Type, response)` / the `ResponseView` accessors) the request-id and
# header-transform modules build on. Do NOT advance this to #409's
# scheme/host/body accessors: neither module needs them.
# The rlib of shared module implementations — re-exported by the cdylib shells.
ephpm-middleware-modules = { path = "crates/ephpm-middleware-modules" }
ephpm-middleware = { git = "https://github.com/ephpm/ephpm.git", rev = "a6e5317718dfa11ed2b7f6970996139b305eed73" }
ephpm-middleware = { git = "https://github.com/ephpm/ephpm.git", rev = "e63284838d07d348e2155e76916daaf9782c012b" }
# Test-only: the same embedded KV store the host wires into the middleware host
# table, so the ratelimit unit tests exercise the real `kv_incr_ttl` path. Same
# rev as the ABI crate so both resolve to one crate instance and the `Store`
# type matches `ephpm_middleware::host::set_kv_store`.
ephpm-kv = { git = "https://github.com/ephpm/ephpm.git", rev = "a6e5317718dfa11ed2b7f6970996139b305eed73" }
ephpm-kv = { git = "https://github.com/ephpm/ephpm.git", rev = "e63284838d07d348e2155e76916daaf9782c012b" }

serde_json = "1"
# ip-allowlist: CIDR parsing + membership for IPv4/IPv6. Default features only
Expand Down
26 changes: 23 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,14 @@ rather than compile into the server.
> the repo is private those downloads require a token. The owner flips it
> public when ready.

ePHPm runs middleware **in front of PHP, before PHP dispatch** — reject,
rewrite, or annotate a request at native speed, with direct access to the
embedded (cluster-replicated) KV store. See the
ePHPm runs middleware in two phases. The **request phase** runs **in front of
PHP, before PHP dispatch** — reject, rewrite, or annotate a request at native
speed, with direct access to the embedded (cluster-replicated) KV store; it
fails **closed**. The optional **response phase** runs **after** the response
is generated (PHP, static file, or error page), in reverse chain order, to
*transform* it — header injection, correlation ids; it fails
**safe** and is not a security gate. A module opts into the response phase with
`declare!(Type, response)`. See the
[Native Middleware guide](https://github.com/ephpm/ephpm/blob/main/site/content/guides/native-middleware.md)
for the operator view and chain semantics.

Expand All @@ -28,6 +33,15 @@ for the operator view and chain semantics.
| `security-headers` | `ephpm-middleware-security-headers` | Append standard security response headers (HSTS, CSP, `X-Frame-Options`, …). |
| `maintenance-mode` | `ephpm-middleware-maintenance-mode` | Flip a tenant into a `503` holding page via a per-site KV flag — no redeploy (`Retry-After`; IP/path bypass; fails **open**). |
| `ip-allowlist` | `ephpm-middleware-ip-allowlist` | Allow/deny requests by client IP against CIDR lists, fail-closed (`403`); deny beats allow. |
| `request-id` | `ephpm-middleware-request-id` | **Request + response phase.** Give every request a correlation id: generate or honor an inbound `X-Request-Id`, inject it for PHP, and echo it on the response. |
| `header-transform` | `ephpm-middleware-header-transform` | **Request + response phase.** Set request headers seen by PHP; set/remove response headers out. |

> **No `compression` module.** Response-body compression is deliberately *not*
> shipped as a middleware: ePHPm's core already compresses buffered responses
> by default (`[server.response] compression`, **on**, brotli-then-gzip),
> negotiating `Accept-Encoding` and running **before** the response phase — so
> a middleware compressor would be redundant and inert on a stock server. Use
> the built-in knob, not a module.

Per-module configuration keys are documented in each crate's module docs
(`crates/ephpm-middleware-<name>/src/lib.rs` re-exports the implementation from
Expand Down Expand Up @@ -103,8 +117,14 @@ crates/
ephpm-middleware-security-headers cdylib shell
ephpm-middleware-maintenance-mode cdylib shell
ephpm-middleware-ip-allowlist cdylib shell
ephpm-middleware-request-id cdylib shell: declare!(RequestId, response)
ephpm-middleware-header-transform cdylib shell: declare!(HeaderTransform, response)
```

The last two opt into the **response phase** with `declare!(Type, response)` —
the host runs their `invoke_response` after the response is generated to
transform it, in addition to their request phase.

The impl/shell split is deliberate: multiple crates each exporting the same
`ephpm_middleware_*` symbols cannot be linked into one binary, so the
implementations live symbol-free in `ephpm-middleware-modules` and each cdylib
Expand Down
21 changes: 21 additions & 0 deletions crates/ephpm-middleware-header-transform/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
[package]
name = "ephpm-middleware-header-transform"
version.workspace = true
edition.workspace = true
rust-version.workspace = true
license.workspace = true
repository.workspace = true
description = "ePHPm native middleware: set request headers seen by PHP and set/remove response headers out (request + response phase; loadable cdylib; implementation in ephpm-middleware-modules)"

[lib]
# cdylib = the loadable module for the dlopen lane; rlib for tests + the
# `vendor-middleware` feature. See ephpm-middleware-jwt for the symbol-collision
# rationale behind the impl/shell split.
crate-type = ["cdylib", "rlib"]

[dependencies]
ephpm-middleware.workspace = true
ephpm-middleware-modules.workspace = true

[lints]
workspace = true
11 changes: 11 additions & 0 deletions crates/ephpm-middleware-header-transform/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//! `header-transform` — loadable cdylib shell around the shared implementation
//! in [`ephpm_middleware_modules::header_transform`].
//!
//! The middleware itself (request/response header set + response header remove,
//! the request + response phase logic, docs and tests included) lives in
//! `ephpm-middleware-modules`. This crate only adds the C ABI exports
//! (`declare!(HeaderTransform, response)`) for the `dlopen` lane.

pub use ephpm_middleware_modules::header_transform::HeaderTransform;

ephpm_middleware::declare!(HeaderTransform, response);
Loading
Loading