I'm building an Actix-Web app to serve the contents of a directory. I'm trying to use all items from a HashMap
in the App
to use as redirects:
use actix_files::{Files, NamedFile};
use actix_web::http::StatusCode;
use actix_web::{web, App};
use actix_web::{HttpRequest, HttpServer, Responder};
use serde::Deserialize;
use std::collections::HashMap;
use std::fs;
use std::path::{Path, PathBuf};
#[derive(Deserialize, Clone, Debug)]
struct RawRedirects(HashMap<String, RawRedirectValue>);
#[derive(Clone, Debug)]
struct Redirects(HashMap<String, RedirectValue>);
#[derive(Deserialize, Clone, Debug)]
struct RawRedirectValue {
status: u16,
destination: String,
}
#[derive(Clone, Debug)]
struct RedirectValue {
status: http::StatusCode,
destination: String,
}
#[derive(Clone, Debug)]
struct AppData {
not_found_file: PathBuf,
}
impl From<RawRedirects> for Redirects {
fn from(val: RawRedirects) -> Self {
val.0
.into_iter()
.map(|(key, value)| {
(
key.trim_end_matches('/').to_string(),
RedirectValue {
status: StatusCode::from_u16(value.status).unwrap(),
destination: value.destination.trim_end_matches('/').to_string(),
},
)
})
.collect()
}
}
impl std::iter::FromIterator<(std::string::String, RedirectValue)> for Redirects {
fn from_iter<T: IntoIterator<Item = (std::string::String, RedirectValue)>>(iter: T) -> Self {
let mut c: Redirects = Redirects(HashMap::new());
for i in iter {
c.0.insert(i.0, i.1);
}
c
}
}
#[actix_web::main]
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
let dir_to_serve: PathBuf = std::env::var("DIR_TO_SERVE")
.unwrap_or("dist".into())
.into();
let port: u16 = std::env::var("PORT")
.unwrap_or("5173".into())
.parse()
.unwrap();
let interface: String = std::env::var("INTERFACE_TO_BIND").unwrap_or("127.0.0.1".into());
let not_found_file: PathBuf = std::env::var("NOT_FOUND_FILE")
.unwrap_or(
Path::new("dist")
.join("404.html")
.to_str()
.unwrap()
.to_string(),
)
.parse()
.unwrap();
let mount_path = std::env::var("MOUNT_PATH").unwrap_or("/".into());
let raw_redirects: RawRedirects = {
let file_content: String = fs::read_to_string("redirects.json")?;
serde_json::from_str(&file_content)?
};
let mut app = App::new()
.app_data(web::Data::new(AppData {
not_found_file: not_found_file.clone(),
}))
.service(
Files::new(&mount_path, &dir_to_serve)
.index_file("index.html")
.redirect_to_slash_directory()
.use_hidden_files()
.default_handler(web::to({
let value: PathBuf = dir_to_serve.clone();
move |req: HttpRequest, app_data: web::Data<AppData>| {
serve_index_file(req, value.clone(), app_data)
}
})),
);
let redirects: Redirects = raw_redirects.clone().into();
for (key, value) in redirects.0 {
app.service(web::Redirect::new(key, value.destination).using_status_code(value.status));
}
let server = HttpServer::new(move || app).bind((interface.to_owned(), port))?;
println!("Docs running on: http://{}:{}", interface, port);
server.run().await?;
Ok(())
}
async fn serve_index_file(
req: HttpRequest,
mut dir_to_serve: PathBuf,
app_data: web::Data<AppData>,
) -> impl Responder {
let path = req.path().trim_start_matches('/');
dir_to_serve.push(path);
dir_to_serve.push("index.html");
match NamedFile::open(dir_to_serve) {
Ok(named_file) => named_file.respond_to(&req),
Err(_) => NamedFile::open(app_data.not_found_file.clone())
.unwrap()
.customize()
.with_status(StatusCode::NOT_FOUND)
.respond_to(&req)
.map_into_boxed_body(),
}
}
However i get this error:
the trait bound `actix_web::App<actix_web::app_service::AppEntry>: std::clone::Clone` is not satisfied in `{closure@docs/src/main.rs:116:34: 116:41}`
within `{closure@docs/src/main.rs:116:34: 116:41}`, the trait `std::clone::Clone` is not implemented for `actix_web::App<actix_web::app_service::AppEntry>`, which is required by `{closure@docs/src/main.rs:116:34: 116:41}: std::clone::Clone`
I understand, that this is because I'm modifying app
variable inside of a for
loop, however I haven't found any alternatives/solutions from either Google or various LLMs.
Any Help is greatly appreciated!
Greetings, Alex