Endpoint body_reader that caches the raw, pre-parser request bytes for
signature verification.
Configure it on the endpoint's Plug.Parsers (the installer patches
this; a router plug CANNOT recover pre-parse bytes):
plug Plug.Parsers,
...,
body_reader: {AshHooks.BodyReader, :read_body, []}The controller then passes conn.private[:ash_hooks_raw_body] to
AshHooks.Ingress.ingest/4 — signature schemes sign over the exact wire
bytes, so any re-encoding (parsed params, JSON roundtrip) would break
verification.
The reader is endpoint-wide by default: every parsed request (not only
webhook routes) carries a second copy of its body in conn.private for
the request lifetime, bounded by the parser's :length. To scope the
memory cost to the webhook routes, pass :only path prefixes:
plug Plug.Parsers,
...,
body_reader: {AshHooks.BodyReader, :read_body, [only: ["/webhooks"]]}Only requests whose path starts with one of the prefixes are cached; everything else passes through with zero retained bytes.
Summary
Functions
body_reader entry point. Plug.Parsers invokes the configured MFA as
apply(mod, fun, [conn, opts | extra_args]) — extras arrive as a THIRD
positional argument, so the arity-3 clause is the one the parser calls;
opts and extra merge.
Functions
@spec read_body(Plug.Conn.t(), keyword(), keyword()) :: {:ok, binary(), Plug.Conn.t()} | {:more, binary(), Plug.Conn.t()} | {:error, term()}
body_reader entry point. Plug.Parsers invokes the configured MFA as
apply(mod, fun, [conn, opts | extra_args]) — extras arrive as a THIRD
positional argument, so the arity-3 clause is the one the parser calls;
opts and extra merge.