fix: ensure domain-based index rewriting works for SPAs

Previously, SPA routes (e.g. `/dashboard`) bypassed custom index rewriting
logic due to `actix_files` intercepting requests before the fallback handler.
This hotfix reorders route registration and introduces explicit handling for
unmatched paths and root (/) to support React-style routing with domain-specific
index.html rewrites.
This commit is contained in:
Andrew 2025-05-05 23:58:18 +07:00
parent 3b8360fa40
commit 85de86f835
3 changed files with 65 additions and 29 deletions

2
Cargo.lock generated
View file

@ -1267,7 +1267,7 @@ dependencies = [
[[package]] [[package]]
name = "servant" name = "servant"
version = "0.2.0" version = "0.2.1"
dependencies = [ dependencies = [
"actix-files", "actix-files",
"actix-web", "actix-web",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "servant" name = "servant"
version = "0.2.0" version = "0.2.1"
edition = "2021" edition = "2021"
authors = ["Andrew G. <me@nuark.xyz>"] authors = ["Andrew G. <me@nuark.xyz>"]

View file

@ -1,9 +1,8 @@
use std::path::Path;
use std::collections::HashMap; use std::collections::HashMap;
use actix_files; use actix_files;
use actix_web::{ use actix_web::{
dev::{ServiceRequest, ServiceResponse}, middleware::Logger, App, HttpServer dev::{ServiceRequest, ServiceResponse}, middleware::Logger, web, App, HttpRequest, HttpServer
}; };
use clap::{arg, command, Parser}; use clap::{arg, command, Parser};
use env_logger::Env; use env_logger::Env;
@ -44,13 +43,18 @@ async fn main() -> std::io::Result<()> {
let args = ServantArgs::parse(); let args = ServantArgs::parse();
let bind_addr = format!("{}:{}", args.host, args.port); // Log configured rewrite rules
println!("Listening on http://{}", bind_addr); if !args.rewrite_index.is_empty() {
log::info!("Configured domain-to-index mappings:");
for (domain, path) in &args.rewrite_index {
log::info!(" {} => {}", domain, path);
}
} else {
log::info!("No domain-to-index rewrite rules configured.");
}
let index_path = Path::new(args.serve_dir.as_str()) let bind_addr = format!("{}:{}", args.host, args.port);
.join(args.index_file.as_str()) log::info!("Listening on http://{}", bind_addr);
.display()
.to_string();
let mount_point = args.mount.clone(); let mount_point = args.mount.clone();
let serve_dir = args.serve_dir.clone(); let serve_dir = args.serve_dir.clone();
@ -68,29 +72,61 @@ async fn main() -> std::io::Result<()> {
App::new() App::new()
.wrap(Logger::default()) .wrap(Logger::default())
.route("/", web::get().to({
let index_map = index_map.clone();
let default_index = default_index.clone();
let serve_dir = serve_dir.clone();
move |req: HttpRequest| {
let host = req
.headers()
.get("Host")
.and_then(|h| h.to_str().ok())
.unwrap_or_default()
.to_string();
let index_file = index_map
.get(&host)
.cloned()
.unwrap_or_else(|| format!("{}/{}", serve_dir, default_index));
log::info!("Custom '/' route: serving '{}' for domain '{}'", index_file, host);
async move {
Ok::<_, actix_web::Error>(
actix_files::NamedFile::open(index_file)?.into_response(&req),
)
}
}
}))
.service( .service(
actix_files::Files::new(&mount_point, &serve_dir) actix_files::Files::new(&mount_point, &serve_dir)
.index_file(&default_index) .use_last_modified(true),
.default_handler(move |req: ServiceRequest| { )
.default_service(actix_web::dev::fn_service(move |req: ServiceRequest| {
let (http_req, _payload) = req.into_parts(); let (http_req, _payload) = req.into_parts();
let host = http_req let host = http_req
.headers() .headers()
.get("Host") .get("Host")
.and_then(|h| h.to_str().ok()) .and_then(|h| h.to_str().ok())
.unwrap_or_default(); .unwrap_or_default()
.to_string();
log::info!("Request for domain: '{}'", host);
// Determine the index file based on the host // Determine the index file based on the host
let index_file = index_map let index_file = index_map
.get(host) .get(&host)
.cloned() .cloned()
.unwrap_or_else(|| format!("{}/{}", serve_dir, default_index)); .unwrap_or_else(|| format!("{}/{}", serve_dir, default_index));
// Log which index is being used for which domain
log::info!("Serving file '{}' for domain '{}'", index_file, host);
async move { async move {
let response = actix_files::NamedFile::open(index_file)?.into_response(&http_req); let response = actix_files::NamedFile::open(index_file)?.into_response(&http_req);
Ok(ServiceResponse::new(http_req, response)) Ok(ServiceResponse::new(http_req, response))
} }
}), }))
)
}) })
.bind(bind_addr)? .bind(bind_addr)?
.run() .run()