AshHooks.BodyReader (AshHooks v0.1.0)

Copy Markdown View Source

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

read_body(conn, opts, extra \\ [])

@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.