Search code examples
rustrust-axum

How to return contents as a file download in Axum?


I have a Rust application that is acting as a proxy. From the user perspective there is a web UI front end. This contains a button that when invoked will trigger a GET request to the Rust application. This in turn calls an external endpoint that returns the CSV file.

What I want is have the file download to the browser when the user clicks the button. Right now, the contents of the CSV file are returned to the browser rather than the file itself.

use std::net::SocketAddr;

use axum::{Router, Server};
use axum::extract::Json;
use axum::routing::get;

pub async fn downloadfile() -> Result<Json<String>, ApiError> {
    let filename = ...;
    let endpoint = "http://127.0.0.1:6101/api/CSV/DownloadFile";
    let path = format!("{}?filename={}", endpoint, filename);

    let response = reqwest::get(path).await?;
    let content = response.text().await?;

    Ok(Json(content))
}

pub async fn serve(listen_addr: SocketAddr) {
    let app = Router::new()
        .route("/downloadfile", get(downloadfile));

    Server::bind(&listen_addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

I understand the reason I'm getting the contents is because I'm returning the content string as JSON. This makes sense. However, what would I need to change to return the file itself so the browser downloads it directly for the user?


Solution

  • I've managed to resolve it and now returns the CSV as a file. Here's the function that works:

    use axum::response::Headers;
    use http::header::{self, HeaderName};
    
    pub async fn downloadfile() -> Result<(Headers<[(HeaderName, &'static str); 2]>, String), ApiError> {
        let filename = ...;
        let endpoint = "http://127.0.0.1:6101/api/CSV/DownloadFile";
        let path = format!("{}?filename={}", endpoint, filename);
    
        let response = reqwest::get(path).await?;
        let content = response.text().await?;
    
        let headers = Headers([
            (header::CONTENT_TYPE, "text/csv; charset=utf-8"),
            (header::CONTENT_DISPOSITION, "attachment; filename=\"data.csv\""),
        ]);
    
        Ok((headers, content))
    }
    

    I'm still struggling with being able to set a dynamic file name, but that for another question.