Skip to content

Commit

Permalink
feat: allow configuring stderr when running the server (#127)
Browse files Browse the repository at this point in the history
* feat: allow configuring stderr in the server / worker

* clean: minor code improvements

* clean: fix format issues
  • Loading branch information
Angelmmiguel authored May 8, 2023
1 parent 41cd177 commit 5207684
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 30 deletions.
16 changes: 10 additions & 6 deletions crates/server/src/handlers/worker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use actix_web::{
web::{Bytes, Data},
HttpRequest, HttpResponse,
};
use std::sync::RwLock;
use std::{fs::File, sync::RwLock};
use wws_router::Routes;
use wws_worker::io::WasmOutput;

Expand All @@ -31,6 +31,7 @@ use wws_worker::io::WasmOutput;
/// allowing Actix to select it for us.
pub async fn handle_worker(req: HttpRequest, body: Bytes) -> HttpResponse {
let routes = req.app_data::<Data<Routes>>().unwrap();
let stderr_file = req.app_data::<Data<Option<File>>>().unwrap();
let data_connectors = req
.app_data::<Data<RwLock<DataConnectors>>>()
.unwrap()
Expand Down Expand Up @@ -68,11 +69,14 @@ pub async fn handle_worker(req: HttpRequest, body: Bytes) -> HttpResponse {
None => None,
};

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

let mut builder = HttpResponse::build(
StatusCode::from_u16(handler_result.status).unwrap_or(StatusCode::OK),
Expand Down
15 changes: 14 additions & 1 deletion crates/server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use anyhow::Result;
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::sync::RwLock;
use wws_data_kv::KV;
Expand All @@ -32,11 +33,22 @@ pub async fn serve(
base_routes: Routes,
hostname: &str,
port: u16,
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)?;

stderr_file = Data::new(Some(file));
} else {
stderr_file = Data::new(None);
}

let server = HttpServer::new(move || {
let mut app = App::new()
Expand All @@ -46,7 +58,8 @@ pub async fn serve(
.wrap(middleware::NormalizePath::trim())
.app_data(Data::clone(&routes))
.app_data(Data::clone(&data))
.app_data(Data::clone(&root_path));
.app_data(Data::clone(&root_path))
.app_data(Data::clone(&stderr_file));

// Append routes to the current service
for route in routes.routes.iter() {
Expand Down
43 changes: 21 additions & 22 deletions crates/worker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@

pub mod config;
pub mod io;
mod stdio;

use actix_web::HttpRequest;
use anyhow::{anyhow, Result};
use config::Config;
use io::{WasmInput, WasmOutput};
use std::fs;
use std::fs::{self, File};
use std::path::PathBuf;
use std::{collections::HashMap, path::Path};
use wasi_common::pipe::{ReadPipe, WritePipe};
use stdio::Stdio;
use wasmtime::{Engine, Linker, Module, Store};
use wasmtime_wasi::{Dir, WasiCtxBuilder};
use wws_config::Config as ProjectConfig;
Expand Down Expand Up @@ -72,13 +73,21 @@ impl Worker {
body: &str,
kv: Option<HashMap<String, String>>,
vars: &HashMap<String, String>,
stderr: &Option<File>,
) -> Result<WasmOutput> {
let input = serde_json::to_string(&WasmInput::new(request, body, kv)).unwrap();

// Prepare STDIO
let stdout = WritePipe::new_in_memory();
let stderr = WritePipe::new_in_memory();
let stdin = ReadPipe::from(input);
// Prepare the stderr file if present
let stderr_file;

if let Some(file) = stderr {
stderr_file = Some(file.try_clone()?);
} else {
stderr_file = None;
}

// Initialize stdio and configure it
let stdio = Stdio::new(&input, stderr_file);

let mut linker = Linker::new(&self.engine);
wasmtime_wasi::add_to_linker(&mut linker, |s| s)?;
Expand All @@ -88,11 +97,10 @@ impl Worker {
vars.iter().map(|(k, v)| (k.clone(), v.clone())).collect();

// Create the initial WASI context
let mut wasi_builder = WasiCtxBuilder::new()
.stdin(Box::new(stdin))
.stdout(Box::new(stdout.clone()))
.stderr(Box::new(stderr.clone()))
.envs(&tuple_vars)?;
let mut wasi_builder = WasiCtxBuilder::new().envs(&tuple_vars)?;

// Configure the stdio
wasi_builder = stdio.configure_wasi_ctx(wasi_builder);

// Mount folders from the configuration
if let Some(folders) = self.config.folders.as_ref() {
Expand Down Expand Up @@ -122,17 +130,8 @@ impl Worker {

drop(store);

let err_contents: Vec<u8> = stderr
.try_into_inner()
.map_err(|_err| anyhow::Error::msg("Nothing to show"))?
.into_inner();

let string_err = String::from_utf8(err_contents)?;
if !string_err.is_empty() {
println!("Error: {string_err}");
}

let contents: Vec<u8> = stdout
let contents: Vec<u8> = stdio
.stdout
.try_into_inner()
.map_err(|_err| anyhow::Error::msg("Nothing to show"))?
.into_inner();
Expand Down
56 changes: 56 additions & 0 deletions crates/worker/src/stdio.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
use std::{fs::File, io::Cursor};
use wasi_common::pipe::{ReadPipe, WritePipe};
use wasmtime_wasi::WasiCtxBuilder;

/// A library to configure the stdio of the WASI context.
/// Note that currently, wws relies on stdin and stdout
/// to send and read data from the worker.
///
/// The stderr is configurable just to cover use cases in which
/// wws is used as a library and we want to expose the logs
///
/// @see https:/vmware-labs/wasm-workers-server/issues/125
///
/// The stdin/stdout approach will change in the future with
/// a more performant and appropiate way.
pub struct Stdio {
/// Defines the stdin ReadPipe to send the data to the module
pub stdin: ReadPipe<Cursor<String>>,
/// Defines the stdout to extract the data from the module
pub stdout: WritePipe<Cursor<Vec<u8>>>,
/// Defines the stderr to expose logs from inside the module
pub stderr: Option<WritePipe<File>>,
}

impl Stdio {
/// Initialize the stdio. The stdin will contain the input data.
pub fn new(input: &str, stderr_file: Option<File>) -> Self {
let stderr;

if let Some(file) = stderr_file {
stderr = Some(WritePipe::new(file));
} else {
stderr = None
}

Self {
stdin: ReadPipe::from(input),
stdout: WritePipe::new_in_memory(),
stderr,
}
}

pub fn configure_wasi_ctx(&self, builder: WasiCtxBuilder) -> WasiCtxBuilder {
let builder = builder
.stdin(Box::new(self.stdin.clone()))
.stdout(Box::new(self.stdout.clone()));

// Set stderr if it was previously configured. If not, inherit
// it from the environment
if let Some(pipe) = self.stderr.clone() {
builder.stderr(Box::new(pipe))
} else {
builder.inherit_stderr()
}
}
}
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async fn main() -> std::io::Result<()> {
);
}

let server = serve(&args.path, routes, &args.hostname, args.port)
let server = serve(&args.path, routes, &args.hostname, args.port, None)
.await
.map_err(|err| Error::new(ErrorKind::AddrInUse, err))?;

Expand Down

0 comments on commit 5207684

Please sign in to comment.