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: use more descriptive types for enabling features on serve #185

Merged
merged 1 commit into from
Aug 30, 2023
Merged
Show file tree
Hide file tree
Changes from all 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
9 changes: 6 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,14 @@ 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
7 changes: 6 additions & 1 deletion crates/router/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ use std::path::{Path, PathBuf};
use std::time::Instant;
use wws_config::Config;

pub use route::Route;
pub use route::{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 +55,10 @@ impl Routes {
Self { routes, prefix }
}

pub fn iter(&self) -> impl Iterator<Item = &Route> {
self.routes.iter()
}
ereslibre marked this conversation as resolved.
Show resolved Hide resolved

/// 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
39 changes: 36 additions & 3 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<WorkerSet> = RwLock::new(WorkerSet::default());
}

/// Identify if a route can manage a certain URL and generates
Expand All @@ -37,13 +40,36 @@ 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,
}

/// Structure that holds the map of workers from their identifier to
/// their worker::Worker structure containing the runtime information,
/// such as the WebAssembly module itself as well as the WebAssembly
/// runtime.
///
/// This structure hides the global (but internal) hash map from
/// other crates.
#[derive(Default)]
pub struct WorkerSet {
workers: HashMap<String, Worker>,
}

impl WorkerSet {
pub fn get(&self, worker_id: &str) -> Option<&Worker> {
self.workers.get(worker_id)
}

pub fn register(&mut self, worker_id: String, worker: Worker) {
self.workers.insert(worker_id, worker);
}
}

impl Route {
Expand All @@ -57,12 +83,19 @@ impl Route {
prefix: &str,
project_config: &ProjectConfig,
) -> Self {
let worker = Worker::new(base_path, &filepath, project_config).unwrap();
let worker =
Worker::new(base_path, &filepath, project_config).expect("error creating worker");
let worker_id = worker.id.clone();

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

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

Expand Down
11 changes: 6 additions & 5 deletions crates/server/src/handlers/assets.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
// Copyright 2022 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::AppData;
use actix_files::NamedFile;
use actix_web::{web::Data, HttpRequest};
use std::{
io::{Error, ErrorKind},
path::PathBuf,
};
use std::io::{Error, ErrorKind};

/// Find a static HTML file in the `public` folder. This function is used
/// when there's no direct file to be served. It will look for certain patterns
/// 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> {
let root_path = req.app_data::<Data<PathBuf>>().unwrap();
let root_path = &req
.app_data::<Data<AppData>>()
.expect("error fetching app data")
.root_path;
let uri_path = req.path();

// File path. This is required for the wasm_handler as dynamic routes may capture static files
Expand Down
7 changes: 5 additions & 2 deletions crates/server/src/handlers/not_found.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// Copyright 2022 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use crate::AppData;
use actix_files::NamedFile;
use actix_web::{web::Data, HttpRequest, HttpResponse};
use std::path::PathBuf;

/// This method tries to render a custom 404 error file from the static
/// folder. If not, it will render an empty 404
pub async fn handle_not_found(req: &HttpRequest) -> HttpResponse {
let root_path = req.app_data::<Data<PathBuf>>().unwrap();
let root_path = &req
.app_data::<Data<AppData>>()
.expect("error fetching app data")
.root_path;
let public_404_path = root_path.join("public").join("404.html");

if let Ok(file) = NamedFile::open_async(public_404_path).await {
Expand Down
68 changes: 35 additions & 33 deletions crates/server/src/handlers/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
// SPDX-License-Identifier: Apache-2.0

use super::{assets::handle_assets, not_found::handle_not_found};
use crate::DataConnectors;
use crate::{AppData, DataConnectors};
use actix_web::{
http::StatusCode,
web::{self, Bytes, Data},
web::{Bytes, Data},
HttpRequest, HttpResponse,
};
use std::{fs::File, io::Write, sync::RwLock};
use wws_router::Routes;
use std::{io::Write, sync::RwLock};
use wws_router::WORKERS;
use wws_worker::io::WasmOutput;

const CORS_HEADER: &str = "Access-Control-Allow-Origin";
Expand All @@ -31,23 +31,17 @@ const CORS_HEADER: &str = "Access-Control-Allow-Origin";
///
/// For these reasons, we are selecting the right handler at this point and not
/// allowing Actix to select it for us.
pub async fn handle_worker(
req: HttpRequest,
body: Bytes,
cors_origins: web::Data<Option<Vec<String>>>,
) -> HttpResponse {
let routes = req.app_data::<Data<Routes>>().unwrap();
let stderr_file = req.app_data::<Data<Option<File>>>().unwrap();
pub async fn handle_worker(req: HttpRequest, body: Bytes) -> HttpResponse {
let app_data = req
.app_data::<Data<AppData>>()
.expect("error fetching app data");
let data_connectors = req
.app_data::<Data<RwLock<DataConnectors>>>()
.unwrap()
.clone();
// We will improve error handling
.expect("error fetching data connectors");
let result: HttpResponse;

// First, we need to identify the best suited route
let selected_route = routes.retrieve_best_route(req.path());

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
Expand All @@ -58,38 +52,46 @@ pub async fn handle_worker(
}
}

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) => {
let connector = data_connectors.read().unwrap();
let connector = data_connectors
.read()
.expect("error locking data connectors lock for reading");
let kv_store = connector.kv.find_store(namespace);

kv_store.map(|store| store.clone())
}
None => None,
};

let stderr_file = app_data
.stderr
.as_ref()
.map(|file| file.try_clone().expect("error setting up stderr"));

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) {
Ok(output) => (output, true),
Err(error) => {
if let Some(stderr_file) = stderr_file.get_ref() {
if let Ok(mut stderr_file) = stderr_file.try_clone() {
stderr_file
.write_all(error.to_string().as_bytes())
.expect("Failed to write error to stderr_file");
} else {
eprintln!("{}", error);
}
if let Some(mut stderr_file) = stderr_file {
stderr_file
.write_all(error.to_string().as_bytes())
.expect("Failed to write error to stderr_file");
} else {
eprintln!("{}", error);
}
Expand All @@ -104,7 +106,7 @@ pub async fn handle_worker(
builder.insert_header(("Content-Type", "text/html"));

// Check if cors config has any origins to register
if let Some(origins) = cors_origins.as_ref() {
if let Some(origins) = app_data.cors_origins.as_ref() {
// Check if worker has overridden the header, if not
if !handler_result.headers.contains_key(CORS_HEADER) {
// insert those origins in 'Access-Control-Allow-Origin' header
Expand All @@ -123,7 +125,7 @@ pub async fn handle_worker(
if handler_success && kv_namespace.is_some() {
data_connectors
.write()
.unwrap()
.expect("error locking data connectors lock for writing")
.kv
.replace_store(&kv_namespace.unwrap(), &handler_result.kv)
}
Expand Down
Loading