如何在服务器中创建文件并在按下按钮时下载到本地?

问题描述 投票:0回答:1

我的网络开发经验为零。这听起来是一个相当简单的实现方法,但我想我不知道在线搜索时要使用的正确关键字,因为我还没有走得很远。

我想建立一个网站,每当我访问

127.0.0.1:8080/some_number
时,它都会显示 2 个按钮,分别是
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
}

有人可以向我指出一些关于如何从这里继续前进的资源吗?

谢谢

rust rust-axum
1个回答
0
投票

这应该可以解决问题。 运行

touch
不是必需的,在 Rust 中可以使用
File::create
创建文件。

/ 端点返回请求文件所需的 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, io::AsyncWriteExt};

#[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:8080").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);
    if file.exists() {
        fs::read(file).await.ok()
    } else {
        let contents: Vec<u8> = b"new file!".into();
        let mut file = fs::File::create(file).await.ok()?;
        file.write_all(&contents).await.ok()?;
        file.flush().await.ok()?;
        Some(contents)
    }
}

async fn index() -> Html<Vec<u8>> {
    let html = fs::read("index.html").await.unwrap();
    Html(html)
}
© www.soinside.com 2019 - 2024. All rights reserved.