Hi folks,
For some reason, the server component of the apache
http-components [1] library doesn't get any love from the Clojure
community. This is particularly strange to me, given the
early/broad adoption of the client component (i.e. clj-http). In
any case, I took version 5 for a spin, and in particular the
async-server aspect of it, as I wanted to see whether it would
integrate cleanly/nicely with core.async channels.
The ring model of async handlers taking 3 arguments, albeit
necessary, is hard to work with. Not only does it feel kind of
magic - it is actually non-trivial to (correctly) propagate the
`respond` and `raise` callbacks across the middleware stack (which
can lead to bugs). The way I see it, it would be much nicer if the
response's body was enough to distinguish whether this is a sync
or async response. In other words, returning a core.async channel
as the body should suffice to treat this as an async response.
I spent a few hours today, and came up with this server wrapper
[2]. If you need convincing that this works, evaluate the two
expressions at the bottom of the file (inside a `comment`
expression), and cURL into it from your terminal (per the
comments). The actual construct which enables a channel to act as
body can be seen here (`ChannelContentProducer`)
[3].
The convention (for handlers) is simple. Use `promise-chan` for single response, but regular `chan` for streaming responses. That is it! No respond/raise indirection - just putting into the channel (and closing it when streaming). Other than that, honors the ring-spec (wrt requests).
So there you have it - async from the ground-up, HTTP2
supporting, ring-friendly web-server prototype (in less than 250
lines).
Kind regards,
Dimitrios
[1]: https://hc.apache.org/httpcomponents-core-5.0.x/index.html
[2]:
https://github.com/jimpil/asynctopia/blob/master/src/asynctopia/server/embedded.clj
[3]:
https://github.com/jimpil/asynctopia/blob/master/src/asynctopia/server/channel_body.clj