How to return a `Vec<u8>` as file from Axum?

Hi,

I'm trying to generate a PDF with genpdf, render it to a buffer (e.g. Vec<u8>) and then return it from a route handler as a file.

The crucial part of the code looks like this:

// Load font
let font_family = ...

// Create document
let mut doc: genpdf::Document = ...

// Create buffer
let mut pdf_buf: Vec<u8> = vec![];

// Render PDF
doc.render(&mut pdf_buf).expect("Failed to render PDF.");

// Convert buffer to stream
let stream = ReaderStream::new(&*pdf_buf);

// Convert stream to axum HTTP body
let body = StreamBody::new(stream);

// Headers
let headers = AppendHeaders([
    (header::CONTENT_TYPE, "application/pdf"),
    (header::CONTENT_DISPOSITION, "inline; filename=\"document.pdf\"")
]);

// Return
(headers, body)

The line

let stream = ReaderStream::new(&*pdf_buf);

gives me the following error:

error[E0597]: `pdf_buf` does not live long enough
  --> src/main.rs:55:38
   |
55 |     let stream = ReaderStream::new(&*pdf_buf);
   |                                      ^^^^^^^ borrowed value does not live long enough
...
58 |     let body = StreamBody::new(stream);
   |                ----------------------- argument requires that `pdf_buf` is borrowed for `'static`
...
68 | }
   | - `pdf_buf` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `axum-pdf-route` due to previous error

Unfortunately I don't know how to get a 'static lifetime on pdf_buf or somehow convert the the Vec<u8> otherwise. I tried using tokio::io::BufWriter but didn't get it to work.

Maybe someone can help me out on this problem.
Thanks in advance :slight_smile:

You shouldn't use ReaderStream when you're just reading from a Vec<u8>. If you want to use a StreamBody, then you can just put the vector in a futures::stream::once, but in your case, you should use a http_body::Full instead.

let body = Full::new(pdf_buf);

Thanks a lot for your answer @alice. This helped me a lot.
I first tried to use Full::new(pdf_buf) exactly like you suggested, which means that body is of type Full<Vec<u8>>. As IntoResponse is not implemented for Full<Vec<u8>> I did the following:

// Create buffer
let mut pdf_buf: Vec<u8> = vec![];

// Render PDF
doc.render(&mut pdf_buf).expect("Failed to render PDF.");

// Convert stream to axum HTTP body
let bytes = Bytes::from(pdf_buf);
let body = Full::new(bytes);

// Headers
let headers = AppendHeaders([
    (header::CONTENT_TYPE, "application/pdf"),
    (header::CONTENT_DISPOSITION, "inline; filename=\"document.pdf\"")
]);

// Return
(headers, body)

I now use the bytes crate to convert the Vec<u8> to Bytes and then put these into http_body::Full. With this the body is of type Full<Bytes> for which IntoResponse is implemented. Now it works :partying_face:

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.