У меня нулевой опыт веб-разработки. Кажется, что реализовать это довольно просто, но я думаю, что не знаю правильных ключевых слов для использования при поиске в Интернете, потому что я не продвинулся очень далеко.
Я хочу создать веб-сайт таким образом, чтобы всякий раз, когда я захожу на 127.0.0.1:8080/some_number, на нем отображались две кнопки с надписью Download csv и Download txt.
Когда я нажимаю кнопку (предположим Download csv), она должна сделать следующее:
<some_number>.csv на сервере.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
}
Может ли кто-нибудь указать мне на некоторые ресурсы о том, как двигаться дальше?
Спасибо

это должно сработать.
конечная точка / возвращает 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)
}
Нп, наверное, лучше использовать tokio::process::Command::spawn. Я обновлю код в своем ответе!
Большое спасибо. Я постараюсь понять все, что здесь происходит. На всякий случай: я упомянул
touchпросто в качестве примера; на самом деле мне нужно запуститьmy_system_executable, который генерирует нужный мне файл. В этом случае мне следует просто заменить вашFile::createнаstd::process::Command::spawn, верно?