RpcElixir.Plug mounts a router over HTTP. It reads seven options at init/1.
Only :router is required. The rest have safe defaults.
| Option | Default | What it does |
|---|---|---|
:router | required | The module that uses RpcElixir.Router. |
:path_prefix | "/rpc" | Stripped from the request path before dispatch. |
:ctx_builder | nil | Builds the base RpcElixir.Context from the conn. |
:max_body_size | 8 MB (8 * 1024 * 1024) | Request body cap in bytes. |
:max_body_depth | 64 | Nesting-depth cap on the decoded JSON body. |
:require_content_type | true | Requires content-type: application/json. |
:allowed_origins | nil (disabled) | Allow-list of origin strings. |
:path_prefix
The plug only answers POST. The path must start with the prefix and a slash.
The rest of the path is the wire name. So POST /rpc/users.get dispatches the
procedure "users.get". An empty remainder returns the not-found error. Any
other path returns it too.
:require_content_type
Every request must carry content-type: application/json. The media type is
compared without case. Parameters such as a charset are allowed. A wrong type
or a missing header is rejected with unsupported_media_type at status 415.
Turning this off removes the main CSRF defence. The header requirement stops
the request being a simple cross-site request. Browsers must then send a CORS
preflight, which this plug never approves. HTML forms cannot send
application/json at all. Setting this to false removes that defence. You
then need another one in its place. A token check or a strict
:allowed_origins list are the usual choices.
The bundled JavaScript client always sends the header. A hand-written curl or
fetch call that omits it will fail with 415.
:allowed_origins
This is an allow-list and it is disabled by default. When you pass a list, the
plug reads the origin request header. A request with no origin header
passes. Otherwise the first header value must appear in your list. The match
is an exact string compare. A value outside the list is rejected with
forbidden at status 403. Its details.reason is "forbidden_origin". There
is no wildcard and no pattern matching.
:max_body_size and :max_body_depth
Both are denial-of-service limits. The body is read in 1 MB chunks. A large
request never lands in memory at once. Exceeding the byte cap returns
payload_too_large at status 413.
The depth cap runs right after decoding. It bounds the stack and CPU that the
byte cap alone cannot. A body deeper than the cap returns
input_validation_failed at status 400. Its details.reason is
"body_too_deep". The body must also decode to a JSON object. An empty body
counts as {}. See Handling errors for the full code table.
:ctx_builder
The builder is a (Plug.Conn.t() -> RpcElixir.Context.t()) function. Its
result becomes the base context. The transport then always overwrites :req
with conn metadata. The conn and socket fields stay nil unless your
builder sets them. See Writing middleware for what to do
with the context.
Session integration
Plug.Session and a session fetch must run before this plug. Order matters.
defmodule MyApp.Router do
use Plug.Builder
plug Plug.Session,
store: :cookie, key: "_my_app_session", signing_salt: "my_salt"
plug :fetch_session
plug RpcElixir.Plug, router: MyApp.RpcRouter
endThe plug reads the session into ctx.req.session. Middleware writes it back
through RpcElixir.Resolution. An unfetched session reads as an empty map
instead of nil.
The :expose_error_details application key
This one is application config, not a plug option. Set it under the
:elixir_ts_rpc application. It defaults to false.
config :elixir_ts_rpc, expose_error_details: trueWhen it is false, crash details and body-read failures are withheld from the
response. Keep it false in production. See Handling errors for the
error shape rules.