trillium-rs / trillium

Trillium is a composable toolkit for building internet applications with async rust

Home Page:https://trillium.rs

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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.