3

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?

kmdreko
  • 42,554
  • 6
  • 57
  • 106
JGilmartin
  • 8,683
  • 14
  • 66
  • 85
  • To tell a browser to download a file instead of loading it in the browser as text, you set the `Content-Disposition: attachment; filename="MyFileName.csv"` header. And you probably want to set `Content-Type: text/csv`. https://stackoverflow.com/questions/393647/response-content-type-as-csv – PitaJ Aug 11 '22 at 20:42

1 Answers1

3

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.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
JGilmartin
  • 8,683
  • 14
  • 66
  • 85
  • This answer is for axum 0.4 and the api changed slightly from 0.5, but basically all you have to do is remove the `Headers` wrapper type; it is not needed any more. – kmdreko Sep 19 '22 at 00:17