Skip to content

Commit

Permalink
feat: use more descriptive types for enabling features on serve
Browse files Browse the repository at this point in the history
  • Loading branch information
ereslibre committed Aug 24, 2023
1 parent 8b8c333 commit 21ef21e
Show file tree
Hide file tree
Showing 7 changed files with 127 additions and 65 deletions.
10 changes: 7 additions & 3 deletions crates/api-manage/src/handlers/v0/workers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use actix_web::{
web::{Data, Json, Path},
HttpResponse, Responder, Result,
};
use wws_router::Routes;
use wws_router::{Routes, WORKERS};

/// Return the list of loaded workers.
#[utoipa::path(
Expand All @@ -34,11 +34,15 @@ pub async fn handle_api_workers(routes: Data<Routes>) -> Result<impl Responder>
)]
#[get("/_api/v0/workers/{id}")]
pub async fn handle_api_worker(routes: Data<Routes>, path: Path<String>) -> HttpResponse {
let workers = WORKERS
.read()
.expect("error locking worker lock for reading");
let worker = routes
.routes
.iter()
.find(|r| &r.worker.id == path.as_ref())
.map(|r| &r.worker);
.find(|r| &r.worker == path.as_ref())
.map(|r| workers.get(&r.worker))
.expect("unexpected missing worker");

if let Some(worker) = worker {
HttpResponse::Ok().json(WorkerConfig::from(worker))
Expand Down
14 changes: 11 additions & 3 deletions crates/api-manage/src/models/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use serde::Serialize;
use utoipa::ToSchema;
use wws_router::Route;
use wws_router::{Route, WORKERS};

#[derive(Serialize, ToSchema)]
/// Defines a worker in a given application.
Expand All @@ -23,10 +23,18 @@ pub struct Worker {

impl From<&Route> for Worker {
fn from(value: &Route) -> Self {
let name = value.worker.config.name.as_ref();
let workers = WORKERS
.read()
.expect("error locking worker lock for reading");
let name = workers
.get(&value.worker)
.expect("unexpected missing worker")
.config
.name
.as_ref();

Self {
id: value.worker.id.clone(),
id: value.worker.clone(),
name: name.unwrap_or(&String::from("default")).to_string(),
path: value.path.clone(),
filepath: value.handler.to_string_lossy().to_string(),
Expand Down
6 changes: 6 additions & 0 deletions crates/router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ use std::time::Instant;
use wws_config::Config;

pub use route::Route;
pub use route::WORKERS;

/// Contains all registered routes
#[derive(Clone, Default)]
pub struct Routes {
pub routes: Vec<Route>,
pub prefix: String,
Expand Down Expand Up @@ -54,6 +56,10 @@ impl Routes {
Self { routes, prefix }
}

pub fn iter(&self) -> impl Iterator<Item = &Route> {
self.routes.iter()
}

/// Based on a set of routes and a given path, it provides the best
/// match based on the parametrized URL score. See the [`Route::can_manage_path`]
/// method to understand how to calculate the score.
Expand Down
14 changes: 12 additions & 2 deletions crates/router/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
use lazy_static::lazy_static;
use regex::Regex;
use std::{
collections::HashMap,
ffi::OsStr,
path::{Component, Path, PathBuf},
sync::RwLock,
};
use wws_config::Config as ProjectConfig;
use wws_worker::Worker;

lazy_static! {
static ref PARAMETER_REGEX: Regex = Regex::new(r"\[\w+\]").unwrap();
static ref DYNAMIC_ROUTE_REGEX: Regex = Regex::new(r".*\[\w+\].*").unwrap();
pub static ref WORKERS: RwLock<HashMap<String, Worker>> = RwLock::new(HashMap::default());
}

/// Identify if a route can manage a certain URL and generates
Expand All @@ -37,13 +40,14 @@ pub enum RouteAffinity {
/// api/index.wasm => /api
/// api/v2/ping.wasm => /api/v2/ping
/// ```
#[derive(Clone)]
pub struct Route {
/// The wasm module that will manage the route
pub handler: PathBuf,
/// The URL path
pub path: String,
/// The associated worker
pub worker: Worker,
pub worker: String,
}

impl Route {
Expand All @@ -58,11 +62,17 @@ impl Route {
project_config: &ProjectConfig,
) -> Self {
let worker = Worker::new(base_path, &filepath, project_config).unwrap();
let worker_id = worker.id.clone();

WORKERS
.write()
.expect("error locking worker lock for writing")
.insert(worker_id.clone(), worker);

Self {
path: Self::retrieve_route(base_path, &filepath, prefix),
handler: filepath,
worker,
worker: worker_id.clone(),
}
}

Expand Down
20 changes: 12 additions & 8 deletions crates/server/src/handlers/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use actix_web::{
HttpRequest, HttpResponse,
};
use std::{fs::File, io::Write, sync::RwLock};
use wws_router::Routes;
use wws_router::{Routes, WORKERS};
use wws_worker::io::WasmOutput;

/// Process an HTTP request by passing it to the right Runner. The Runner
Expand Down Expand Up @@ -41,7 +41,6 @@ pub async fn handle_worker(req: HttpRequest, body: Bytes) -> HttpResponse {

// First, we need to identify the best suited route
let selected_route = 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
Expand All @@ -52,12 +51,20 @@ pub async fn handle_worker(req: HttpRequest, body: Bytes) -> HttpResponse {
}
}

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

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

// Let's continue
let body_str = String::from_utf8(body.to_vec()).unwrap_or_else(|_| String::from(""));

// Init from configuration
let vars = &route.worker.config.vars;
let kv_namespace = route.worker.config.data_kv_namespace();
let vars = &worker.config.vars;
let kv_namespace = worker.config.data_kv_namespace();

let store = match &kv_namespace {
Some(namespace) => {
Expand All @@ -70,10 +77,7 @@ pub async fn handle_worker(req: HttpRequest, body: Bytes) -> HttpResponse {
};

let (handler_result, handler_success) =
match route
.worker
.run(&req, &body_str, store, vars, stderr_file.get_ref())
{
match worker.run(&req, &body_str, store, vars, stderr_file.get_ref()) {
Ok(output) => (output, true),
Err(error) => {
if let Some(stderr_file) = stderr_file.get_ref() {
Expand Down
109 changes: 70 additions & 39 deletions crates/server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,84 +16,115 @@ use actix_web::{
use handlers::assets::handle_assets;
use handlers::not_found::handle_not_found;
use handlers::worker::handle_worker;
use std::fs::OpenOptions;
use std::path::Path;
use std::path::PathBuf;
use std::sync::RwLock;
use wws_api_manage::config_manage_api_handlers;
use wws_data_kv::KV;
use wws_panel::config_panel_handlers;
use wws_router::Routes;
use wws_router::{Routes, WORKERS};

#[derive(Clone, PartialEq)]
pub enum Panel {
Enabled,
Disabled,
}

impl From<bool> for Panel {
fn from(panel_enabled: bool) -> Self {
if panel_enabled {
Panel::Enabled
} else {
Panel::Disabled
}
}
}

#[derive(Default)]
pub(crate) struct DataConnectors {
kv: KV,
}

#[derive(Clone)]
pub struct ServeOptions {
pub root_path: PathBuf,
pub base_routes: Routes,
pub hostname: String,
pub port: u16,
pub panel: Panel,
}

#[derive(Default)]
struct AppData {
routes: Routes,
data: RwLock<DataConnectors>,
root_path: PathBuf,
}

impl From<ServeOptions> for AppData {
fn from(serve_options: ServeOptions) -> Self {
AppData {
routes: serve_options.base_routes,
data: RwLock::new(DataConnectors::default()),
root_path: serve_options.root_path,
}
}
}

/// Initializes an actix-web server based on the given configuration and
/// path. It will configure the different handlers to manage static
/// assets and workers.
pub async fn serve(
root_path: &Path,
base_routes: Routes,
hostname: &str,
port: u16,
panel: bool,
stderr: Option<&Path>,
) -> Result<Server> {
// Initializes the data connectors. For now, just KV
let data = Data::new(RwLock::new(DataConnectors::default()));
let routes = Data::new(base_routes);
let root_path = Data::new(root_path.to_owned());
let stderr_file;

// Configure stderr
if let Some(path) = stderr {
let file = OpenOptions::new()
.read(true)
.write(true)
.open(path)
.map_err(|_| errors::ServeError::InitializeServerError)?;

stderr_file = Data::new(Some(file));
} else {
stderr_file = Data::new(None);
}
pub async fn serve(serve_options: ServeOptions) -> Result<Server> {
let (hostname, port) = (serve_options.hostname.clone(), serve_options.port);
let serve_options = serve_options.clone();

let server = HttpServer::new(move || {
// Initializes the app data for handlers
let app_data: Data<AppData> =
Data::new(<ServeOptions as Into<AppData>>::into(serve_options.clone()));
let workers = WORKERS
.read()
.expect("error locking worker lock for reading");

let mut app = App::new()
// enable logger
.wrap(middleware::Logger::default())
// Clean path before sending it to the service
.wrap(middleware::NormalizePath::trim())
.app_data(Data::clone(&routes))
.app_data(Data::clone(&data))
.app_data(Data::clone(&root_path))
.app_data(Data::clone(&stderr_file));
.app_data(app_data.clone());

// Configure panel
if panel {
if serve_options.panel == Panel::Enabled {
app = app.configure(config_panel_handlers);
app = app.configure(config_manage_api_handlers);
}

// Append routes to the current service
for route in routes.routes.iter() {
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) = route.worker.config.data_kv_namespace() {
data.write().unwrap().kv.create_store(&namespace);
if let Some(namespace) = worker.config.data_kv_namespace() {
app_data
.data
.write()
.expect("cannot retrieve shared data")
.kv
.create_store(&namespace);
}
}

// Serve static files from the static folder
let mut static_prefix = routes.prefix.clone();
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, root_path.join("public"))
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,
Expand Down
19 changes: 9 additions & 10 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use std::process::exit;
use wws_config::Config;
use wws_project::{identify_type, prepare_project, ProjectType};
use wws_router::Routes;
use wws_server::serve;
use wws_server::{serve, ServeOptions};

// Arguments
#[derive(Parser, Debug)]
Expand Down Expand Up @@ -191,20 +191,19 @@ async fn main() -> std::io::Result<()> {
);
}

let server = serve(
&project_path,
routes,
&args.hostname,
args.port,
args.enable_panel,
None,
)
let server = serve(ServeOptions {
root_path: project_path,
base_routes: routes,
hostname: args.hostname.clone(),
port: args.port,
panel: args.enable_panel.into(),
})
.await
.map_err(|err| Error::new(ErrorKind::AddrInUse, err))?;

println!(
"🚀 Start serving requests at http://{}:{}\n",
&args.hostname, args.port
args.hostname, args.port
);

// Run the server
Expand Down

0 comments on commit 21ef21e

Please sign in to comment.