-
Notifications
You must be signed in to change notification settings - Fork 36
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: load static assets manually so all other requests rely on wasm_handler #226
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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, | ||
|
@@ -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); | ||
} | ||
} | ||
|
||
Comment on lines
-45
to
-53
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since we're explicitly mounting the static files in certain routes, this is not required anymore. |
||
if let Some(route) = selected_route { | ||
let workers = WORKERS | ||
.read() | ||
.expect("error locking worker lock for reading"); | ||
|
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; | ||
|
@@ -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"); | ||
|
||
Comment on lines
+82
to
+106
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I extracted all the initialization befure the |
||
// 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( | ||
|
@@ -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 | ||
}) | ||
|
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(()) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We need this new condition to avoid returning the
public
folder information.