Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add the prefix option to prepend a path to all URLs #37

Merged
merged 4 commits into from
Nov 22, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ struct Args {
/// Folder to read WebAssembly modules from
#[clap(value_parser, default_value = ".")]
path: PathBuf,

/// Prepend the given path to all URLs
#[clap(long, default_value = "")]
prefix: String,
}

// Common structures
Expand Down Expand Up @@ -163,9 +167,12 @@ async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();

// Initialize the prefix
let prefix = router::format_prefix(&args.prefix);

println!("⚙️ Loading routes from: {}", &args.path.display());
let routes = Data::new(Routes {
routes: router::initialize_routes(&args.path),
routes: router::initialize_routes(&args.path, &prefix),
});

let data = Data::new(RwLock::new(DataConnectors { kv: KV::new() }));
Expand Down Expand Up @@ -210,8 +217,13 @@ async fn main() -> std::io::Result<()> {
}

// Serve static files from the static folder
let mut static_prefix = prefix.clone();
if static_prefix.is_empty() {
static_prefix = String::from("/");
}

app = app.service(
Files::new("/", &args.path.join("public"))
Files::new(&static_prefix, &args.path.join("public"))
.index_file("index.html")
// This handler check if there's an HTML file in the public folder that
// can reply to the given request. For example, if someone request /about,
Expand Down
89 changes: 73 additions & 16 deletions src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ impl Route {
/// proper URL path based on the filename.
///
/// This method also initializes the Runner and loads the Config if available.
fn new(base_path: &Path, filepath: PathBuf) -> Self {
fn new(base_path: &Path, filepath: PathBuf, prefix: &str) -> Self {
let runner = Runner::new(&filepath).unwrap();
// Load configuration
let mut config_path = filepath.clone();
Expand All @@ -56,7 +56,7 @@ impl Route {
}

Self {
path: Self::retrieve_route(base_path, &filepath),
path: Self::retrieve_route(base_path, &filepath, prefix),
handler: filepath,
runner,
config,
Expand All @@ -65,20 +65,21 @@ impl Route {

// Process the given path to return the proper route for the API.
// It will transform paths like test/index.wasm into /test.
fn retrieve_route(base_path: &Path, path: &Path) -> String {
fn retrieve_route(base_path: &Path, path: &Path, prefix: &str) -> String {
// Normalize both paths
let n_path = Self::normalize_path_to_url(path);
let n_base_path = Self::normalize_path_to_url(base_path);

// Remove the base_path
match n_path.strip_prefix(&n_base_path) {
Some(worker_path) => {
if worker_path.is_empty() {
// Index file at root
String::from("/")
} else {
worker_path.to_string()
}
String::from(prefix)
+ (if worker_path.is_empty() {
// Index file at root
"/"
} else {
worker_path
})
}
None => {
// TODO: manage errors properly and skip the route
Expand All @@ -93,7 +94,7 @@ impl Route {
// - Remove file extension
// - Keep only "normal" components. Others like "." or "./" are ignored
// - Remove "index" components
fn normalize_path_to_url(path: &Path) -> String {
pub fn normalize_path_to_url(path: &Path) -> String {
path.with_extension("")
.components()
.filter_map(|c| match c {
Expand All @@ -113,7 +114,7 @@ impl Route {
/// Initialize the list of routes from the given folder. This method will look for
/// all `**/*.wasm` files and will create the associated routes. This routing approach
/// is pretty popular in web development and static sites.
pub fn initialize_routes(base_path: &Path) -> Vec<Route> {
pub fn initialize_routes(base_path: &Path, prefix: &str) -> Vec<Route> {
let mut routes = Vec::new();
let path = Path::new(&base_path);

Expand All @@ -128,7 +129,7 @@ pub fn initialize_routes(base_path: &Path) -> Vec<Route> {
for entry in glob_items {
match entry {
Ok(filepath) => {
routes.push(Route::new(base_path, filepath));
routes.push(Route::new(base_path, filepath, prefix));
}
Err(e) => println!("Could not read the file {:?}", e),
}
Expand All @@ -137,6 +138,35 @@ pub fn initialize_routes(base_path: &Path) -> Vec<Route> {
routes
}

/// Defines a prefix in the context of the application.
/// This prefix will be used for the static assets and the
/// handlers.
///
/// A prefix must have the format: /X. This method receives
/// the optional prefix and returns a proper String.
///
/// To be flexible, the method will manage "windows" paths too:
/// \app. This shouldn't be considered as "prefix" must be an URI
/// path. However, the check is pretty simple, so we will consider
/// it.
pub fn format_prefix(source: &str) -> String {
let mut normalized_prefix = source.to_string();
// Ensure the prefix doesn't include any \ character
normalized_prefix = normalized_prefix.replace('\\', "/");

if !normalized_prefix.is_empty() {
Angelmmiguel marked this conversation as resolved.
Show resolved Hide resolved
if !normalized_prefix.starts_with('/') {
normalized_prefix = String::from('/') + &normalized_prefix;
}

if normalized_prefix.ends_with('/') {
normalized_prefix.pop();
}
}

normalized_prefix
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -172,7 +202,7 @@ mod tests {

for t in tests {
assert_eq!(
Route::retrieve_route(&Path::new(t.0), &PathBuf::from(t.1)),
Route::retrieve_route(&Path::new(t.0), &PathBuf::from(t.1), ""),
String::from(t.2),
)
}
Expand Down Expand Up @@ -221,7 +251,7 @@ mod tests {

for t in tests {
assert_eq!(
Route::retrieve_route(&Path::new(t.0), &PathBuf::from(t.1)),
Route::retrieve_route(&Path::new(t.0), &PathBuf::from(t.1), ""),
String::from(t.2),
)
}
Expand Down Expand Up @@ -274,7 +304,7 @@ mod tests {

for t in tests {
assert_eq!(
Route::retrieve_route(&Path::new(t.0), &PathBuf::from(t.1)),
Route::retrieve_route(&Path::new(t.0), &PathBuf::from(t.1), ""),
String::from(t.2),
)
}
Expand Down Expand Up @@ -339,9 +369,36 @@ mod tests {

for t in tests {
assert_eq!(
Route::retrieve_route(&Path::new(t.0), &PathBuf::from(t.1)),
Route::retrieve_route(&Path::new(t.0), &PathBuf::from(t.1), ""),
String::from(t.2),
)
}
}

#[test]
fn format_provided_prefix() {
let tests = [
// Unix approach
("", ""),
("/app", "/app"),
("app", "/app"),
("app/", "/app"),
("/app/", "/app"),
("/app/test/", "/app/test"),
("/app/test", "/app/test"),
("app/test/", "/app/test"),
// Windows approach
("\\app", "/app"),
("app", "/app"),
("app\\", "/app"),
("\\app\\", "/app"),
("\\app\\test\\", "/app/test"),
("\\app\\test", "/app/test"),
("app\\test\\", "/app/test"),
];

for t in tests {
assert_eq!(format_prefix(t.0), String::from(t.1))
}
}
}