Skip to content

Commit

Permalink
feat: load static assets manually so all other requests rely on wasm_…
Browse files Browse the repository at this point in the history
…handler
  • Loading branch information
Angelmmiguel committed Oct 2, 2023
1 parent de99069 commit 4109e3c
Show file tree
Hide file tree
Showing 4 changed files with 142 additions and 67 deletions.
4 changes: 2 additions & 2 deletions crates/server/src/handlers/assets.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use std::io::{Error, ErrorKind};
/// like "public/{uri}/index.html" and "public/{uri}.html".
///
/// If no file is present, it will try to get a default "public/404.html"
pub async fn handle_assets(req: &HttpRequest) -> Result<NamedFile, Error> {
pub async fn handle_assets(req: HttpRequest) -> Result<NamedFile, Error> {
let root_path = &req
.app_data::<Data<AppData>>()
.expect("error fetching app data")
Expand All @@ -25,7 +25,7 @@ pub async fn handle_assets(req: &HttpRequest) -> Result<NamedFile, Error> {
// Same as before, but the file is located at ./about.html
let html_ext_path = root_path.join(format!("public{uri_path}.html"));

if file_path.exists() {
if file_path.exists() && !uri_path.is_empty() && uri_path != "/" {
NamedFile::open_async(file_path).await
} else if uri_path.ends_with('/') && index_folder_path.exists() {
NamedFile::open_async(index_folder_path).await
Expand Down
14 changes: 3 additions & 11 deletions crates/server/src/handlers/worker.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Copyright 2022 VMware, Inc.
// Copyright 2022-2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use super::{assets::handle_assets, not_found::handle_not_found};
use super::not_found::handle_not_found;
use crate::{AppData, DataConnectors};
use actix_web::{
http::StatusCode,
Expand Down Expand Up @@ -42,16 +42,8 @@ pub async fn handle_worker(req: HttpRequest, body: Bytes) -> HttpResponse {

// First, we need to identify the best suited route
let selected_route = app_data.routes.retrieve_best_route(req.path());
if let Some(route) = selected_route {
// First, check if there's an existing static file. Static assets have more priority
// than dynamic routes. However, I cannot set the static assets as the first service
// as it's captures everything.
if route.is_dynamic() {
if let Ok(existing_file) = handle_assets(&req).await {
return existing_file.into_response(&req);
}
}

if let Some(route) = selected_route {
let workers = WORKERS
.read()
.expect("error locking worker lock for reading");
Expand Down
92 changes: 38 additions & 54 deletions crates/server/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
// Copyright 2022 VMware, Inc.
// Copyright 2022-2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

mod errors;
use errors::{Result, ServeError};

mod handlers;
mod static_assets;

use actix_files::Files;
use actix_web::dev::{fn_service, Server, ServiceRequest, ServiceResponse};
use actix_web::dev::Server;
use actix_web::{
middleware,
web::{self, Data},
App, HttpServer,
};
use errors::{Result, ServeError};
use handlers::assets::handle_assets;
use handlers::not_found::handle_not_found;
use handlers::worker::handle_worker;
use static_assets::StaticAssets;
use std::{path::PathBuf, sync::RwLock};
use wws_api_manage::config_manage_api_handlers;
use wws_data_kv::KV;
Expand Down Expand Up @@ -75,11 +74,38 @@ impl From<ServeOptions> for AppData {
/// assets and workers.
pub async fn serve(serve_options: ServeOptions) -> Result<Server> {
// Initializes the data connectors. For now, just KV
let data_connectors = Data::new(RwLock::new(DataConnectors::default()));
let mut data = DataConnectors::default();

let (hostname, port) = (serve_options.hostname.clone(), serve_options.port);
let serve_options = serve_options.clone();

let workers = WORKERS
.read()
.expect("error locking worker lock for reading");

// Configure the KV store when required
for route in serve_options.base_routes.routes.iter() {
let worker = workers
.get(&route.worker)
.expect("unexpected missing worker");

// Configure KV
if let Some(namespace) = worker.config.data_kv_namespace() {
data.kv.create_store(&namespace);
}
}

// Pre-create the KV namespaces
let data_connectors = Data::new(RwLock::new(data));

// Static assets
let mut static_assets =
StaticAssets::new(&serve_options.root_path, &serve_options.base_routes.prefix);
static_assets
.load()
.expect("Error loading the static assets");

// Build the actix server with all the configuration
let server = HttpServer::new(move || {
// Initializes the app data for handlers
let app_data: Data<AppData> = Data::new(
Expand All @@ -101,55 +127,13 @@ pub async fn serve(serve_options: ServeOptions) -> Result<Server> {
app = app.configure(config_manage_api_handlers);
}

let workers = WORKERS
.read()
.expect("error locking worker lock for reading");

// Append routes to the current service
for route in app_data.routes.iter() {
app = app.service(web::resource(route.actix_path()).to(handle_worker));

let worker = workers
.get(&route.worker)
.expect("unexpected missing worker");

// Configure KV
if let Some(namespace) = worker.config.data_kv_namespace() {
data_connectors
.write()
.expect("cannot retrieve shared data")
.kv
.create_store(&namespace);
}
// Mount static assets
for actix_path in static_assets.paths.iter() {
app = app.route(actix_path, web::get().to(handle_assets));
}

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

app = app.service(
Files::new(&static_prefix, app_data.root_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,
// this handler will look for a /public/about.html file.
.default_handler(fn_service(|req: ServiceRequest| async {
let (req, _) = req.into_parts();

match handle_assets(&req).await {
Ok(existing_file) => {
let res = existing_file.into_response(&req);
Ok(ServiceResponse::new(req, res))
}
Err(_) => {
let res = handle_not_found(&req).await;
Ok(ServiceResponse::new(req, res))
}
}
})),
);
// Default all other routes to the Wasm handler
app = app.default_service(web::route().to(handle_worker));

app
})
Expand Down
99 changes: 99 additions & 0 deletions crates/server/src/static_assets.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// Copyright 2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::{
ffi::OsStr,
io::Error as IoError,
path::{Path, PathBuf},
};

/// Folder that contains the static assets in a wws project
pub const STATIC_ASSETS_FOLDER: &str = "public";

/// Load and stores the information of static assets
/// in Wasm Workers Server. It enables to manually set
/// the list of routes in actix.
#[derive(Default)]
pub struct StaticAssets {
/// Static assets folder
folder: PathBuf,
/// The initial prefix to mount the static assets
prefix: String,
/// List of local paths to set in actix
pub paths: Vec<String>,
}

impl StaticAssets {
/// Creates a new instance by looking at the public
/// folder if exists.
pub fn new(root_path: &Path, prefix: &str) -> Self {
Self {
folder: root_path.join(STATIC_ASSETS_FOLDER),
prefix: prefix.to_string(),
paths: Vec::new(),
}
}

/// Load the assets in the public folder.
pub fn load(&mut self) -> Result<(), IoError> {
if self.folder.exists() {
// Set the provided prefix
let prefix = self.prefix.clone();

self.load_folder(&self.folder.clone(), &prefix)?;
}

Ok(())
}

/// Load the assets from a specific folder
fn load_folder(&mut self, folder: &Path, prefix: &str) -> Result<(), IoError> {
let paths = folder.read_dir()?;

for path in paths {
let path = path?.path();

if path.is_dir() {
let folder_path = path
.file_stem()
.expect("Error reading the file stem from a static file")
.to_string_lossy();

let new_prefix = format!("{}/{}", prefix, folder_path);
// Recursive
self.load_folder(&path, &new_prefix)?;
} else {
// Save the static file
match path.extension() {
Some(ext) if ext == OsStr::new("html") => {
let stem = path
.file_stem()
.expect("Error reading the file name from a static file")
.to_string_lossy();

// Add the full file path
self.paths.push(format!("{prefix}/{stem}.html"));

if stem == "index" {
// For index files, mount it on the prefix (folder)
self.paths.push(format!("{prefix}/"));
} else {
// Mount it without the .html (pretty routes)
self.paths.push(format!("{prefix}/{stem}"));
}
}
_ => {
let name = path
.file_name()
.expect("Error reading the file name from a static file")
.to_string_lossy();

self.paths.push(format!("{prefix}/{name}"));
}
}
}
}

Ok(())
}
}

0 comments on commit 4109e3c

Please sign in to comment.