Example of how to return a file dynamically #608
-
I am having a hard time trying to return binary data from one of my routes. My use case is that when a Would it be possible to include an example that shows how to:
|
Beta Was this translation helpful? Give feedback.
Replies: 8 comments 35 replies
-
You can do this: use axum::{
body::StreamBody,
http::{header, StatusCode},
response::{Headers, IntoResponse},
routing::get,
Router,
};
use std::net::SocketAddr;
use tokio_util::io::ReaderStream;
#[tokio::main]
async fn main() {
let app = Router::new().route("/", get(handler));
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
axum::Server::bind(&addr)
.serve(app.into_make_service())
.await
.unwrap();
}
async fn handler() -> impl IntoResponse {
// `File` implements `AsyncRead`
let file = match tokio::fs::File::open("Cargo.toml").await {
Ok(file) => file,
Err(err) => return Err((StatusCode::NOT_FOUND, format!("File not found: {}", err))),
};
// convert the `AsyncRead` into a `Stream`
let stream = ReaderStream::new(file);
// convert the `Stream` into an `axum::body::HttpBody`
let body = StreamBody::new(stream);
let headers = Headers([
(header::CONTENT_TYPE, "text/toml; charset=utf-8"),
(
header::CONTENT_DISPOSITION,
"attachment; filename=\"Cargo.toml\"",
),
]);
Ok((headers, body))
} |
Beta Was this translation helpful? Give feedback.
-
I'm sorry to comment on a really old discussion but I'm new to axum and I'm trying to send an image based on the path given by the request. I based my code on the answer because I cannot find how to do what I'm trying to do with axum but the
Here is my code to have more context about what I'm trying to do: #[derive(Deserialize)]
pub struct QueryParams {
path: String,
}
pub async fn get_image(
Query(QueryParams { path }): Query<QueryParams>
) -> impl IntoResponse {
let filename = match PathBuf::from(path).file_name() {
Some(name) => name,
None => return Err((StatusCode::BAD_REQUEST, "File name couldn't be determined".to_string()))
};
let file = match tokio::fs::File::open(path).await {
Ok(file) => file,
Err(err) => return Err((StatusCode::NOT_FOUND, format!("File not found: {}", err)))
};
let content_type = match mime_guess::from_path(&path).first_raw() {
Some(mime) => mime,
None => return Err((StatusCode::BAD_REQUEST, "MIME Type couldn't be determined".to_string()))
};
let stream = ReaderStream::new(file);
let body = StreamBody::new(stream);
let headers = Headers([
(header::CONTENT_TYPE, content_type),
(
header::CONTENT_DISPOSITION,
&format!("attachment; filename=\"{:?}\"", filename),
),
]);
Ok((headers, body))
}
|
Beta Was this translation helpful? Give feedback.
-
Hello. I have a similar problem. pub async fn download(state: State<AppState>, Path(path): Path<String>) -> impl IntoResponse {
info!("id: {:?}", path);
let model = QueryCore::find_post_by_id(&state.conn, 17)
.await
.expect("Cannot find posts in page")
.unwrap();
let f : Vec<u8> = model.file.unwrap();
info!("f: {:?}", f);
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("YourFileName.toml")
.await
.unwrap();
file.write(&f).await.unwrap();
file.flush().await.unwrap();
file.sync_all().await.unwrap();
file.shutdown().await.unwrap();
let mut contents = vec![];
file.read_to_end(&mut contents).await.unwrap();
info!("contents: {:?}", contents);
let stream = ReaderStream::new(file);
let body = StreamBody::new(stream);
let headers = [
(header::CONTENT_TYPE, "text/toml; charset=utf-8"),
(
header::CONTENT_DISPOSITION,
"attachment; filename=\"YourFileName.toml\"",
),
];
(headers, body).into_response()
}
pub async fn download2(state: State<AppState>, Path(path): Path<String>) -> impl IntoResponse {
info!("id: {:?}", path);
let model = QueryCore::find_post_by_id(&state.conn, 17)
.await
.expect("Cannot find posts in page")
.unwrap();
let f : Vec<u8> = model.file.unwrap();
info!("f: {:?}", f);
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.open("YourFileName.toml")
.await
.unwrap();
file.write(&f).await.unwrap();
file.flush().await.unwrap();
file.sync_all().await.unwrap();
file.shutdown().await.unwrap();
let mut contents = vec![];
file.read_to_end(&mut contents).await.unwrap();
info!("contents: {:?}", contents);
let body = AsyncReadBody::new(file);
let headers = [
(header::CONTENT_TYPE, "text/toml; charset=utf-8"),
(
header::CONTENT_DISPOSITION,
"attachment; filename=\"YourFileName.toml\"",
),
];
(headers, body).into_response()
} log:
|
Beta Was this translation helpful? Give feedback.
-
Thank you! This was very helpful. Relatedly, this all seems quite critical knowledge for some important use cases. Does this have an example or documentation somewhere? And is the setup still accurate, or have there been improvements in the last 2 years (aside from Headers going away)? |
Beta Was this translation helpful? Give feedback.
-
For future googlers, I would suggest using async fn download_file(headers: HeaderMap) -> Response {
// Do some work...
let path = todo!();
let mut req = Request::new(Body::empty());
*req.headers_mut() = headers;
ServeFile::new(path).try_call(req).await.unwrap()
} This is much simpler, and supports features like range requests as well. |
Beta Was this translation helpful? Give feedback.
-
This thread should be updated since in 0.7, |
Beta Was this translation helpful? Give feedback.
-
It worked for me. But how can I do something when the response or finish is over? |
Beta Was this translation helpful? Give feedback.
-
Hi, I'm trying to download (stream) files stored by Apache Object Store . So I decided to use stream instead. In the document it says:
in this case the type of stream is: So then when I try to create body :
I receive error:
I hope it explains my problem and somebody can help me convert this stream from object_store to Response body. |
Beta Was this translation helpful? Give feedback.
You can do this: