I have zero experience with web development. This sounds like a reasonably simple thing to implement, but I guess I don't know the correct keywords to use when searching online because I haven't gotten very far.
I want to build a website such that whenever I go to 127.0.0.1:8080/some_number
it will show 2 buttons that say Download csv
and Download txt
.
When I press a button (let's assume Download csv
) it should do the following:
<some_number>.csv
already exists in the server.touch <some_number>.csv
) and then download the file.So far I have managed to check if the files exist or not:
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
}
Can someone point me into some resources on how to move forward from here?
Thanks
this should do the trick.
the / endpoint returns the html needed to request a file. If the button is pressed it requests the file from the /{number}/{csv/txt}
endpoint
/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)
}