Skip to content

Commit

Permalink
feat: introduce snapshot2 wasi context building
Browse files Browse the repository at this point in the history
  • Loading branch information
ereslibre committed Sep 29, 2023
1 parent efc787a commit 2885886
Show file tree
Hide file tree
Showing 9 changed files with 390 additions and 224 deletions.
2 changes: 1 addition & 1 deletion crates/api-manage/src/handlers/v0/workers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub async fn handle_api_worker(routes: Data<Routes>, path: Path<String>) -> Http
.map(|r| workers.get(&r.worker).expect("unexpected missing worker"));

if let Some(worker) = worker {
HttpResponse::Ok().json(WorkerConfig::from(worker))
HttpResponse::Ok().json(WorkerConfig::from(worker.as_ref()))
} else {
HttpResponse::NotFound().json("{}")
}
Expand Down
8 changes: 4 additions & 4 deletions crates/router/src/route.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use std::{
collections::HashMap,
ffi::OsStr,
path::{Component, Path, PathBuf},
sync::RwLock,
sync::{Arc, RwLock},
};
use wws_config::Config as ProjectConfig;
use wws_worker::Worker;
Expand Down Expand Up @@ -60,16 +60,16 @@ pub struct Route {
/// other crates.
#[derive(Default)]
pub struct WorkerSet {
workers: HashMap<String, Worker>,
workers: HashMap<String, Arc<Worker>>,
}

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

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

Expand Down
1 change: 1 addition & 0 deletions crates/runtimes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use errors::Result;

mod modules;
mod runtime;
pub use runtime::CtxBuilder;

use modules::{external::ExternalRuntime, javascript::JavaScriptRuntime, native::NativeRuntime};
use std::path::Path;
Expand Down
34 changes: 24 additions & 10 deletions crates/runtimes/src/modules/external.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@

use crate::errors::{self, Result};

use crate::runtime::Runtime;
use crate::runtime::{CtxBuilder, Runtime};
use std::{
fs,
path::{Path, PathBuf},
};
use wasmtime_wasi::{ambient_authority, Dir, WasiCtxBuilder};
use wasmtime_wasi::{ambient_authority, preview2, Dir};
use wws_project::metadata::Runtime as RuntimeMetadata;
use wws_store::Store;

Expand Down Expand Up @@ -90,14 +90,28 @@ impl Runtime for ExternalRuntime {

/// Mount the source code in the WASI context so it can be
/// processed by the engine
fn prepare_wasi_ctx(&self, builder: &mut WasiCtxBuilder) -> Result<()> {
builder
.preopened_dir(
Dir::open_ambient_dir(&self.store.folder, ambient_authority())?,
"/src",
)?
.args(&self.metadata.args)
.map_err(|_| errors::RuntimeError::WasiContextError)?;
fn prepare_wasi_ctx(&self, builder: &mut CtxBuilder) -> Result<()> {
match builder {
CtxBuilder::Preview1(ref mut builder) => {
builder
.preopened_dir(
Dir::open_ambient_dir(&self.store.folder, ambient_authority())?,
"/src",
)?
.args(&self.metadata.args)
.map_err(|_| errors::RuntimeError::WasiContextError)?;
}
CtxBuilder::Preview2(ref mut builder) => {
builder
.preopened_dir(
Dir::open_ambient_dir(&self.store.folder, ambient_authority())?,
preview2::DirPerms::all(),
preview2::FilePerms::all(),
"/src",
)
.args(&self.metadata.args);
}
}

Ok(())
}
Expand Down
26 changes: 19 additions & 7 deletions crates/runtimes/src/modules/javascript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
// SPDX-License-Identifier: Apache-2.0

use crate::errors::Result;
use crate::runtime::Runtime;
use crate::runtime::{CtxBuilder, Runtime};

use std::path::{Path, PathBuf};
use wasmtime_wasi::{ambient_authority, Dir, WasiCtxBuilder};
use wasmtime_wasi::{ambient_authority, preview2, Dir};
use wws_store::Store;

static JS_ENGINE_WASM: &[u8] =
Expand Down Expand Up @@ -46,11 +46,23 @@ impl Runtime for JavaScriptRuntime {

/// Mount the source code in the WASI context so it can be
/// processed by the engine
fn prepare_wasi_ctx(&self, builder: &mut WasiCtxBuilder) -> Result<()> {
builder.preopened_dir(
Dir::open_ambient_dir(&self.store.folder, ambient_authority())?,
"/src",
)?;
fn prepare_wasi_ctx(&self, builder: &mut CtxBuilder) -> Result<()> {
match builder {
CtxBuilder::Preview1(ref mut builder) => {
builder.preopened_dir(
Dir::open_ambient_dir(&self.store.folder, ambient_authority())?,
"/src",
)?;
}
CtxBuilder::Preview2(ref mut builder) => {
builder.preopened_dir(
Dir::open_ambient_dir(&self.store.folder, ambient_authority())?,
preview2::DirPerms::all(),
preview2::FilePerms::all(),
"/src",
);
}
}

Ok(())
}
Expand Down
9 changes: 7 additions & 2 deletions crates/runtimes/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@

use crate::errors::Result;

use wasmtime_wasi::WasiCtxBuilder;
use wasmtime_wasi::{preview2, WasiCtxBuilder};

pub enum CtxBuilder {
Preview1(WasiCtxBuilder),
Preview2(preview2::WasiCtxBuilder),
}

/// Define the behavior a Runtime must have. This includes methods
/// to initialize the environment for the given runtime as well as
Expand All @@ -21,7 +26,7 @@ pub trait Runtime {
/// WASI context builder. This allow runtimes to mount
/// specific lib folders, source code and adding
/// environment variables.
fn prepare_wasi_ctx(&self, _builder: &mut WasiCtxBuilder) -> Result<()> {
fn prepare_wasi_ctx(&self, _builder: &mut CtxBuilder) -> Result<()> {
Ok(())
}

Expand Down
125 changes: 63 additions & 62 deletions crates/server/src/handlers/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,10 @@ pub async fn handle_worker(req: HttpRequest, body: Bytes) -> HttpResponse {
let data_connectors = req
.app_data::<Data<RwLock<DataConnectors>>>()
.expect("error fetching data connectors");
let result: 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 {
let worker = 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.
Expand All @@ -56,74 +55,76 @@ pub async fn handle_worker(req: HttpRequest, body: Bytes) -> HttpResponse {
.read()
.expect("error locking worker lock for reading");

let worker = workers
.get(&route.worker)
.expect("unexpected missing worker");
Some(
workers
.get(&route.worker)
.expect("unexpected missing worker")
.clone(),
)
} else {
None
};

// Let's continue
let body_str = String::from_utf8(body.to_vec()).unwrap_or_else(|_| String::from(""));
if worker.is_none() {
return handle_not_found(&req).await;
};
let worker = worker.unwrap();

// Init from configuration
let vars = &worker.config.vars;
let kv_namespace = worker.config.data_kv_namespace();
let body_str = String::from_utf8(body.to_vec()).unwrap_or_else(|_| String::from(""));

let store = match &kv_namespace {
Some(namespace) => {
let connector = data_connectors
.read()
.expect("error locking data connectors lock for reading");
let kv_store = connector.kv.find_store(namespace);
// Init from configuration
let vars = &worker.config.vars;
let kv_namespace = worker.config.data_kv_namespace();

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

let (handler_result, handler_success) = match worker.run(&req, &body_str, store, vars) {
Ok(output) => (output, true),
Err(err) => (WasmOutput::failed(err), false),
};

let mut builder = HttpResponse::build(
StatusCode::from_u16(handler_result.status).unwrap_or(StatusCode::OK),
);
// Default content type
builder.insert_header(("Content-Type", "text/html"));

// Check if cors config has any origins to register
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
let header_value = origins.join(",");
builder.insert_header((CORS_HEADER, header_value));
}
}
let store = match &kv_namespace {
Some(namespace) => {
let connector = data_connectors
.read()
.expect("error locking data connectors lock for reading");
let kv_store = connector.kv.find_store(namespace);

for (key, val) in handler_result.headers.iter() {
// Note that QuickJS is replacing the "-" character
// with "_" on property keys. Here, we rollback it
builder.insert_header((key.replace('_', "-").as_str(), val.as_str()));
kv_store.map(|store| store.clone())
}

// Write to the state if required
if handler_success && kv_namespace.is_some() {
data_connectors
.write()
.expect("error locking data connectors lock for writing")
.kv
.replace_store(&kv_namespace.unwrap(), &handler_result.kv)
None => None,
};

let (handler_result, handler_success) = match worker.run(&req, &body_str, store, vars).await {
Ok(output) => (output, true),
Err(err) => (WasmOutput::failed(err), false),
};

let mut builder =
HttpResponse::build(StatusCode::from_u16(handler_result.status).unwrap_or(StatusCode::OK));
// Default content type
builder.insert_header(("Content-Type", "text/html"));

// Check if cors config has any origins to register
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
let header_value = origins.join(",");
builder.insert_header((CORS_HEADER, header_value));
}
}

result = match handler_result.body() {
Ok(res) => builder.body(res),
Err(_) => {
HttpResponse::ServiceUnavailable().body("There was an error running the worker")
}
}
} else {
result = handle_not_found(&req).await;
for (key, val) in handler_result.headers.iter() {
// Note that QuickJS is replacing the "-" character
// with "_" on property keys. Here, we rollback it
builder.insert_header((key.replace('_', "-").as_str(), val.as_str()));
}

result
// Write to the state if required
if handler_success && kv_namespace.is_some() {
data_connectors
.write()
.expect("error locking data connectors lock for writing")
.kv
.replace_store(&kv_namespace.unwrap(), &handler_result.kv)
}

match handler_result.body() {
Ok(res) => builder.body(res),
Err(_) => HttpResponse::ServiceUnavailable().body("There was an error running the worker"),
}
}
Loading

0 comments on commit 2885886

Please sign in to comment.