asynchronousrustrust-tokiohyper

Send big `tokio::fs::File` as response body with hyper 1.0 (in a streaming fashion)


I am writing a static file server with hyper 1.0 and tokio 1.0. When serving a file, I want it to be as fast as possible while allocating the minimum required amount of memory. In other words: I want to stream the file directly from disk to the network, without reading it first.

Specifically: I have a tokio::fs::File and want to turn it into a hyper Body.

use std::path::Path;

async fn handle(path: &Path) -> hyper::Response<???> {
    let file = tokio::fs::File::open(path).await.unwrap();

    // TODO: what here?!

    Response::builder().body(body).unwrap()
}

I know of hyper-body-util, but all the Body, Stream, AsyncRead, Frame terms confuse me.


Solution

  • There are a few keys to this:

    All that put together looks like this:

    use bytes::Bytes;
    use futures::TryStreamExt;
    use http_body_util::{combinators::BoxBody, StreamBody};
    use hyper::body::Frame;
    use std::path::Path;
    use tokio_util::io::ReaderStream;
    
    type FileStreamBody = BoxBody<Bytes, std::io::Error>;
    
    async fn handle(path: &Path) -> hyper::Response<FileStreamBody> {
        let file = tokio::fs::File::open(path).await.unwrap();
    
        let byte_stream = ReaderStream::new(file);
        let frame_stream = byte_stream.map_ok(Frame::data);
        let body = StreamBody::new(frame_stream);
        let boxed_body = BoxBody::new(body);
    
        hyper::Response::builder().body(body).unwrap()
    }
    

    Compare the send_file example in the hyper repository.