Feature suggestion for `trillium-api`: Returning a streaming body
joshtriplett opened this issue · comments
I'd love to write an API function that returns a streaming body. Right now, that requires using conn.set_body
directly. If possible, I'd like to return an object that wraps a provider of AsyncRead
.
I attempted to implement this, and found it difficult because an object containing trillium::Body
can't easily implement Handler
, because it needs to give up ownership of the trillium::Body
, which it can't do in a function that takes &self
.
I think this would make the most sense to add inside of an application, as there isn't currently any distinction between a Handler that's intended to be used once (returned from a trillium-api handler) and a "normal" trillium Handler. Since a streaming body can only be read once, it couldn't have a meaningful definition as a reusable trillium Handler outside of the trillium-api context.
Within an application, however, you can ensure that it's never mounted directly, so you could add something like:
struct StreamingBody(std::sync::Mutex<Option<trillium::Body>>);
#[trillium::async_trait]
impl trillium::Handler for StreamingBody {
async fn run(&self, conn: trillium::Conn) -> trillium::Conn {
conn.with_body(
self.0
.lock()
.unwrap()
.take()
.expect("this handler can only be called once"),
)
}
}
impl StreamingBody {
pub fn new(
async_read: impl futures_lite::AsyncRead + Send + Sync + 'static,
len: Option<u64>,
) -> Self {
Self(std::sync::Mutex::new(Some(trillium::Body::new_streaming(
async_read, len,
))))
}
}
pub fn main() {
trillium_smol::run(trillium_api::api(|_: &mut Conn, (): ()| async move {
StreamingBody::new(futures_lite::io::Cursor::new("this will be chunked"), None)
}));
}
Given that I'm just doing it in one place, I ended up calling .set_body
and then returning ()
as the handler. If I end up doing it more than once, I'll probably switch to something reusable like that.