Expand description
§composure_adapter_tokio
Your Discord bot as a plain HTTP server. Serve a Composure bot from any machine, container or PaaS with tokio and hyper: every Interaction on its own task across all cores, with the same answers as on Cloudflare Workers.
- Server defaults:
GET /health, a 1 MiB body limit, panics isolated to their request, graceful shutdown on Ctrl-C / SIGTERM - Configured from the environment:
DISCORD_PUBLIC_KEY,DISCORD_TOKEN,PORT, and you’re serving; or loadCredentialswithconfig,figment,envy, … - Your data in every Command:
.with_data(..)hands your Bot Data to handlers throughHost<T> - Tracked Background Work: deferred work is awaited on shutdown
- Testable: point the HTTP client at a stand-in Discord with
HttpTransport::with_api_base
§Usage
ⓘ
use composure_adapter_tokio::TokioInteractionBot;
#[tokio::main]
async fn main() -> Result<(), composure_adapter_tokio::Error> {
TokioInteractionBot::from_env()? // DISCORD_PUBLIC_KEY, DISCORD_TOKEN?, DISCORD_APPLICATION_ID?, PORT?
.with_handler(Bot) // an `#[app]`
.serve() // 0.0.0.0:$PORT (8080), until Ctrl-C / SIGTERM
.await
}TokioInteractionBot::new(credentials)takescomposure::Credentialsloaded any way:settings.get::<Credentials>("discord")?withconfig,envy::prefixed("DISCORD_").from_env()?, or built in code.from_env()reads the conventional names plusPORT.- Handlers get a
Host<T>State: your Bot Data from.with_data(..)(also viaDeref, andHasDatafor platform-agnostic Commands), plus the bot token, anHttpTransport(asyncreqwest, rustls), and Background Work as tokio tasks.Ctx<impl Platform>commands,ctx.defer(..)andctx.bot()work as on Workers. - Handler futures are
Send(ADR 0012), so handlers cantokio::spawnwork that owns aDeferredCtxor a clone of theHost. HttpTransport::with_api_base(url)sends what would go to Discord’s API elsewhere: a proxy, or a stand-in Discord in tests.- REST outside Interactions:
bot.host().bot()(needs a bot token and an application id in the Credentials), andhost.spawn(..)for tasks shutdown should wait for. - Same answers as the Cloudflare Adapter (both go through
composure::adapter): 401 on a bad signature, 400 on a bad body, ephemeral error messages for handler errors, files through the callback endpoint then 202, Webhook Events on/eventsACKed with 204. On top:GET /healthis 200, bodies over 1 MiB are 413, a panicking handler is 500. - Shutdown stops accepting and waits up to 30s (
.with_grace(..)) for in-flight requests and Background Work..with_shutdown(future)replaces the Ctrl-C / SIGTERM signal. - Plain HTTP only: Discord needs HTTPS, so run it behind a TLS-terminating proxy or tunnel.
- Logs through
tracing; install a subscriber (e.g.tracing_subscriber::fmt) to see them, Composure’sinteraction/background_work/event/restspans included.
See examples/tokio for the example bot served this way.
§License
Licensed under the Apache License, Version 2.0.
Structs§
- Credentials
- The application’s public key (required, verifies Interactions), and optionally the bot token
(for
Ctx::bot) and the application id (for a Bot Client outside Interactions). - Handler
Error - Why an Interaction could not be answered normally. Adapters log it and answer with
HandlerError::responseinstead. - Host
- The State Commands get on a hosted server: your data
T(also viaDeref), plus whatPlatformneeds: the bot token, anHttpTransport, and tokio tasks for Background Work, which graceful shutdown waits for. Soctx.client(),ctx.bot()(with a bot token) andctx.defer(..)work. - Http
Transport Transportover an asyncreqwest::Client(rustls), sleeping on tokio’s timer. Responses keep only theDISCORD_HEADERS(rate limits andcontent-type). Cloning shares the client’s connection pool.- NoHandler
- Handles nothing: every Interaction gets the
InteractionHandlerdefaults - Tokio
Interaction Bot - Interaction bot on a hosted server: an HTTP server on tokio answering Discord’s signed
requests, each Interaction on its own task (ADR 0012). The handler receives a
Host(your data plus the bot token, Transport, and Background Work) as its State. Webhook Events on the events path (default/events) go to the same handler’sEventHandlerimplementation;GET /healthanswers 200. Plain HTTP: put it behind a TLS-terminating proxy or tunnel.
Enums§
- Credentials
Error - Why
Credentialscould not be built - Error
- Why the bot could not be configured or served
Constants§
- DEFAULT_
EVENTS_ PATH - Path Webhook Events are served on unless the Adapter is told otherwise; every other path serves Interactions
- DEFAULT_
GRACE - How long shutdown waits for in-flight requests and Background Work by default
- DEFAULT_
PORT - Port served on when
PORTis not set - HEALTH_
PATH - Path answering
GETwith 200, for load balancers and orchestrators - MAX_
BODY - Requests with a bigger body are answered with 413; Discord’s are a few KB
Traits§
- Event
Handler - Answers Webhook Events. Every method defaults to ignoring the event; override the ones you
care about, or
handleto see every event. Registered on an App with#[app(.., events = MyEvents)]. - Interaction
Handler - Answers Interactions; the one contract every Adapter talks to.
Sis the Adapter’s State.