A Plug.Parsers body reader that caches the raw request body in
conn.assigns[:raw_body] before returning it for JSON parsing.
SquatchMail.SNS.MessageVerifier/SquatchMail.SNS.Processor.process/2 need
the exact bytes SNS sent (to rebuild the signed string and, incidentally,
because Processor.process/2 does its own JSON decode) - by the time
Plug.Parsers hands a controller a parsed conn.params, the original body
is gone unless something captured it first.
Do you need this?
Usually no. squatch_mail_dashboard pipes the webhook route through
SquatchMail.SNS.RawBodyPlug, which captures the raw body automatically —
hosts don't have to wire anything. This module remains as an optional
Plug.Parsers :body_reader for hosts that prefer to capture the raw body
at the endpoint themselves; RawBodyPlug detects an already-set
conn.assigns[:raw_body] and stands down, so the two never fight.
Note that a :body_reader alone is not sufficient for SNS: SNS sends
Content-Type: text/plain; charset=UTF-8, which Plug.Parsers matches no
parser for, so it never invokes the :body_reader for a real SNS request.
That is exactly why RawBodyPlug (which reads unconditionally, after the
parsers pass the body through unread) is the reliable default.
Wiring this in (optional)
Plug.Parsers accepts a :body_reader option that must be a {module, function, args} tuple implementing the same contract as
Plug.Conn.read_body/2 ({:ok, body, conn} | {:more, partial, conn} | {:error, term}), invoked once per chunk. Point it at this module wherever
the host endpoint configures Plug.Parsers:
plug Plug.Parsers,
parsers: [:json],
json_decoder: Jason,
body_reader: {SquatchMail.SNS.RawBodyReader, :read_body, []}This must run before :json parsing consumes the body.
Summary
Functions
Drop-in replacement for Plug.Conn.read_body/2 that additionally appends
every chunk read to conn.assigns[:raw_body] (starting from ""), so the
full raw body is available under that key once parsing completes -
including when the body arrives in multiple chunks ({:more, ...}).
Functions
@spec read_body( Plug.Conn.t(), keyword() ) :: {:ok, binary(), Plug.Conn.t()} | {:more, binary(), Plug.Conn.t()} | {:error, term()}
Drop-in replacement for Plug.Conn.read_body/2 that additionally appends
every chunk read to conn.assigns[:raw_body] (starting from ""), so the
full raw body is available under that key once parsing completes -
including when the body arrives in multiple chunks ({:more, ...}).