LemonChannels.InboundHttp (lemon_channels v0.1.0)

View Source

Optional inbound HTTP listener for channel adapters.

Every adapter shipped before this module was a client — a poller, a websocket client, or a bridge process. Adapters that instead need to receive HTTP (an email webhook, for instance) had nowhere to bind a port, which is why those surfaces stayed in lemon_gateway. This is the smallest thing that closes that gap: one Bandit listener, off unless configured, dispatching by the first path segment to a registered handler.

Configuration

config :lemon_channels, LemonChannels.InboundHttp,
  enabled: true,
  port: 4090,
  ip: {127, 0, 0, 1}

Disabled by default. When disabled the supervisor starts with no children, so nothing binds a port and adapters that need inbound HTTP simply never receive anything — the same failure mode as an unconfigured poller.

Registering a handler

Adapters register a path segment at boot and implement LemonChannels.InboundHttp.Handler:

LemonChannels.InboundHttp.register("email", MyAdapter.Webhook)

A request to /email/anything is then routed to MyAdapter.Webhook.handle_inbound/1, which receives the Plug.Conn (already parsed) and returns it having sent a response.

Registration is deliberately runtime rather than compile-time so satellite packages can add listeners without this app knowing they exist.

Summary

Functions

Returns a specification to start this module under a supervisor.

Whether the listener is configured to bind a port.

Looks up the handler registered for a path segment.

All registered segment => handler pairs.

Configured bind address, loopback by default.

Largest request body the listener will read, in bytes.

Configured port. Only meaningful when enabled?/0.

Registers handler for the first path segment segment.

Functions

child_spec(init_arg)

Returns a specification to start this module under a supervisor.

See Supervisor.

enabled?()

@spec enabled?() :: boolean()

Whether the listener is configured to bind a port.

handler_for(segment)

@spec handler_for(binary()) :: module() | nil

Looks up the handler registered for a path segment.

handlers()

@spec handlers() :: %{optional(binary()) => module()}

All registered segment => handler pairs.

ip()

@spec ip() :: :inet.ip_address()

Configured bind address, loopback by default.

max_body_bytes()

@spec max_body_bytes() :: pos_integer()

Largest request body the listener will read, in bytes.

2 MB by default — deliberately well under Plug.Parsers' 8 MB, since a webhook endpoint is reachable by anyone who learns the URL and this is what bounds the memory one request can cost. Over the limit the caller gets a 413.

Raise it for an adapter that must accept large payloads, remembering that attachments are usually base64-encoded on the wire and so occupy about a third more than their decoded size:

config :lemon_channels, LemonChannels.InboundHttp, max_body_bytes: 8_000_000

LemonChannels.Adapters.Email derives its own attachment cap from this, so the two cannot contradict each other.

port()

@spec port() :: non_neg_integer()

Configured port. Only meaningful when enabled?/0.

register(segment, handler)

@spec register(binary(), module()) :: :ok | {:error, :not_running}

Registers handler for the first path segment segment.

Returns :ok, or {:error, :not_running} when the listener supervisor is not started (which is the case in apps that never start lemon_channels).

Last registration wins. Two packages claiming the same segment is a misconfiguration rather than a supported layout — the loser's webhooks would silently arrive at the winner — so a replacement is logged at warning level. Registration order between two applications is not something either of them controls, which is exactly why this must not be quiet.

start_link(opts \\ [])

@spec start_link(keyword()) :: Supervisor.on_start()