2

In actix-web, it is possible to serve a file by returning in a handler:

HttpResponse::Ok().streaming(file)

But here, file must implement the Stream<Item = Result<Bytes, E>> trait. The File type from the crate async_std does not implement it, so I created a wrapper that implements it:

struct FileStreamer {
    file: File,
}

impl Stream for FileStreamer {
    type Item = Result<Bytes, std::io::Error>;

    fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
        let mut buf = [0; 1024];
        self.file.read(&mut buf).poll_unpin(cx).map(|r| {
            r.map(|n| {
                if n == 0 {
                    None
                } else {
                    Some(Bytes::copy_from_slice(&buf[0..n]))
                }
            })
            .transpose()
        })
    }
}

It works but there is a problem. For every call to read we create a new instance of Bytes, which is a dynamically allocated buffer.

Is this the most efficient way to serve a file in actix-web?

It also feels to me, choosing the right buffer size in that case is actually more critical, as a small buffer will cause repetitive syscalls, and a too large buffer will cause slow memory allocation, that wont even be used entirely.

Am I right to consider recurring dynamic allocation as a performance issue?

PS: The file in question is not static, it is subject to modifications and deletion, for this reason, controlling the reading process is necessary.

uben
  • 1,221
  • 2
  • 11
  • 20

1 Answers1

2

From the actix-web documentation.

actix-web will send the file in question based on a path. This example takes a dynamic path from the URL. I feel you are overthinking the problem of streaming a file.

use actix_files::NamedFile;
use actix_web::{HttpRequest, Result};
use std::path::PathBuf;

async fn index(req: HttpRequest) -> Result<NamedFile> {
    let path: PathBuf = req.match_info().query("filename").parse().unwrap();
    Ok(NamedFile::open(path)?)
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    use actix_web::{web, App, HttpServer};

    HttpServer::new(|| App::new().route("/{filename:.*}", web::get().to(index)))
        .bind("127.0.0.1:8080")?
        .run()
        .await
}
Adam Comer
  • 492
  • 2
  • 10
  • Well, you are probably right, I might be overthinking the problem. But I want to control the reading process of the file, because it is not a static file, it can be deleted by another handler (I plan to use mutex or other sync tools, to prevent concurrent actions) – uben Jan 06 '22 at 23:38
  • I don't think you need any Arcs or Mutexs. You can check if the file is on the filesystem and update/delete it accordingly. – Adam Comer Jan 07 '22 at 01:12