Current section

Files

Jump to
accrue lib accrue webhook caching_body_reader.ex
Raw

lib/accrue/webhook/caching_body_reader.ex

defmodule Accrue.Webhook.CachingBodyReader do
@moduledoc """
Custom body reader for `Plug.Parsers` that tees the raw body into
`conn.assigns[:raw_body]` for webhook signature verification.
Only used inside the `:accrue_webhook_raw_body` pipeline -- never
globally. This ensures that non-webhook routes are unaffected by
raw-body capture.
## Usage
pipeline :accrue_webhook_raw_body do
plug Plug.Parsers,
parsers: [:json],
pass: ["*/*"],
json_decoder: Jason,
body_reader: {Accrue.Webhook.CachingBodyReader, :read_body, []},
length: 1_000_000
end
## Implementation
Chunks are prepended (O(1) cons) during streaming reads and reversed
at flatten time in the consuming plug. This avoids quadratic binary
concatenation when the body arrives in multiple chunks.
"""
@doc """
Reads the request body and tees it into `conn.assigns[:raw_body]`.
Returns `{:ok, body, conn}`, `{:more, body, conn}`, or `{:error, reason}`
matching the `Plug.Conn.read_body/2` contract expected by `Plug.Parsers`.
"""
@spec read_body(Plug.Conn.t(), keyword()) ::
{:ok, binary(), Plug.Conn.t()}
| {:more, binary(), Plug.Conn.t()}
| {:error, term()}
def read_body(conn, opts) do
case Plug.Conn.read_body(conn, opts) do
{:ok, body, conn} ->
conn = update_in(conn.assigns[:raw_body], &[body | &1 || []])
{:ok, body, conn}
{:more, body, conn} ->
conn = update_in(conn.assigns[:raw_body], &[body | &1 || []])
{:more, body, conn}
{:error, reason} ->
{:error, reason}
end
end
end