Как создать файл на сервере и загрузить локально при нажатии кнопки?

У меня нулевой опыт веб-разработки. Кажется, что реализовать это довольно просто, но я думаю, что не знаю правильных ключевых слов для использования при поиске в Интернете, потому что я не продвинулся очень далеко.

Я хочу создать веб-сайт таким образом, чтобы всякий раз, когда я захожу на 127.0.0.1:8080/some_number, на нем отображались две кнопки с надписью Download csv и Download txt.

Когда я нажимаю кнопку (предположим Download csv), она должна сделать следующее:

  1. Проверьте, существует ли файл <some_number>.csv на сервере.
  2. Если да, то просто скачайте его.
  3. Если его не существует, запустите внешнюю команду, которая создаст файл на сервере (скажем, touch <some_number>.csv), а затем загрузите файл.

Пока мне удалось проверить, существуют ли файлы или нет:

use axum::extract::Path;
use axum::{routing::get, Router};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/", get(|| async { "Hello, World!" }))
        .route("/:run_number", get(check_files));

    let listener = tokio::net::TcpListener::bind("127.0.0.1:8080")
        .await
        .unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn check_files(Path(run_number): Path<u32>) -> String {
    let mut response = String::new();

    let csv_file = format!("{run_number}.csv");
    let csv_file = std::path::Path::new(&csv_file);
    if csv_file.exists() {
        response.push_str(&format!("File `{}` does exist\n", csv_file.display()));
    } else {
        response.push_str(&format!("File `{}` does not exist\n", csv_file.display()));
    }

    let txt_file = format!("{run_number}.txt");
    let txt_file = std::path::Path::new(&txt_file);
    if txt_file.exists() {
        response.push_str(&format!("File `{}` does exist", txt_file.display()));
    } else {
        response.push_str(&format!("File `{}` does not exist", txt_file.display()));
    }

    response
}

Может ли кто-нибудь указать мне на некоторые ресурсы о том, как двигаться дальше?

Спасибо

Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
2
0
84
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

это должно сработать.

конечная точка / возвращает HTML-код, необходимый для запроса файла. Если кнопка нажата, она запрашивает файл из конечной точки /{number}/{csv/txt}.

/index.html

<!DOCTYPE html>
<html>
    <body>
        <a href = "/1/csv" download> Download csv</a>
        <a href = "/1/txt" download> Download txt</a>
    </body>
</html>

/src/main.rs

use axum::{
    extract::Path,
    http::{header, HeaderMap},
    response::Html,
    routing::get,
    Router,
};
use tokio::{fs, process};

#[tokio::main]
async fn main() {
    let app = Router::new()
        .route("/:number/:file_type", get(file_handler))
        .route("/", get(index));
    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
    axum::serve(listener, app).await.unwrap();
}

async fn file_handler(
    Path((number, filetype)): Path<(i32, String)>,
) -> Result<(HeaderMap, Vec<u8>), String> {
    if filetype != "csv" && filetype != "txt" {
        return Err("filetype not supported".into());
    };

    let file_name = format!("{number}.{filetype}");

    let Some(file_contents) = read_and_create_file(&file_name).await else {
        return Err("Could not read/create file".into());
    };
    let mut headers = HeaderMap::new();
    headers.insert(
        header::CONTENT_TYPE,
        format!("text/{filetype}; charset=utf-8").parse().unwrap(),
    );
    headers.insert(
        header::CONTENT_DISPOSITION,
        format!("attachment; filename=\"{file_name}\"")
            .parse()
            .unwrap(),
    );
    Ok((headers, file_contents))
}

async fn read_and_create_file(file_name: &str) -> Option<Vec<u8>> {
    let file = std::path::Path::new(file_name);
    println!("{file_name}");
    if !file.exists() {
        let _status_code = process::Command::new("touch")
            .arg(file_name)
            .spawn()
            .ok()?
            .wait()
            .await;
    }
    fs::read(file).await.ok()
}

async fn index() -> Html<Vec<u8>> {
    let html = fs::read("index.html").await.unwrap();
    Html(html)
}

Большое спасибо. Я постараюсь понять все, что здесь происходит. На всякий случай: я упомянул touch просто в качестве примера; на самом деле мне нужно запустить my_system_executable, который генерирует нужный мне файл. В этом случае мне следует просто заменить ваш File::create на std::process::Command::spawn, верно?

DJDuque 10.05.2024 09:22

Нп, наверное, лучше использовать tokio::process::Command::spawn. Я обновлю код в своем ответе!

joeydewaal 13.05.2024 14:41

Другие вопросы по теме