ASGI 3.0 for MoonBit
moonasgi
One typed contract between a server and an application. A scope goes in, events come out, and the transport stays out of your code.
moon add moonbitstack/moonasgi
The contract
Three scopes cover everything a server can hand an application. Three callables cover everything the two say to each other. Nothing else crosses the boundary.
pub(all) enum Scope {
Http(HttpScope)
WebSocket(WebSocketScope)
Lifespan(LifespanScope)
}
pub type Receive = async () -> Event // pull the next event in
pub type Send = async (Event) -> Unit // push the next event out
pub type AsgiApp = async (Scope, Receive, Send) -> Unit
An application is anything with that shape. A handler is the short version, and the server lifts it on the way in:
pub type Handler = (Request) -> Response
pub type Middleware = (Handler) -> Handler
Numbers
The whole surface, counted.
http, websocket, lifespan. Each one a typed record, not a dictionary.
Every message in both directions, extensions included, plus an Other for your own.
run_http, run_http_stream, run_http_scoped, ws_run, run_lifespan. No socket, no event loop.
CORS, gzip, trusted hosts, HTTPS redirect, proxy headers, body limit, server errors.
The pieces
Eleven packages, but you import one. Each is a slice of the same seam, and none of them carries a transport.
The contract
spec holds the scopes, the events, the extension flags and the HTTP/2 lowering. It has no dependencies.
Request and response
http gives you a Request to read and a Response to return, then lowers them onto events.
WebSocket and lifespan
A handler written as connect / receive / disconnect; a service written as startup / shutdown. Both run in a test.
Middleware
mid wraps any application, whatever built it. Write one as a plain function and it stacks with the rest.
One server, many applications
dispatch picks by path prefix or host name, and runs one lifespan across all of them.
Applications from before
wsgi runs a synchronous application on the same events. spec does the same for the older double-callable shape.
One request
A handler, lifted onto the seam and driven start to finish:
fn greet(request : @moonasgi.Request) -> @moonasgi.Response {
@moonasgi.Response::text("hello from the seam")
}
let app : @moonasgi.AsgiApp = @moonasgi.to_asgi(greet)
Two events out, one in, and the handler never learned there was a socket. The event timeline plays the same recording for eight more shapes of request, one event at a time.