Skip to content

Commit 16b39ad

Browse files
fix: allow for use of both manual & fetchEvent based HTTP
This commit updates the componentize-js code to support both fetchEvent and manual based HTTP. To make this possible we needed to re-introduce a few things: - A feature that represents fetch-event - Allow controlling removal of the the built-in fetch event - Allow skipping checks for manual wasi:http/incoming-handler impls - Add oxc-parser to detect manual impls and toggle fetch-event feature The docs are also updated to note the changes to API/usage.
1 parent c8a54cf commit 16b39ad

File tree

12 files changed

+535
-155
lines changed

12 files changed

+535
-155
lines changed

README.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,16 +184,29 @@ The default set of features includes:
184184
* `'random'`: Support for cryptographic random, depends on `wasi:random`. **When disabled, random numbers will still be generated but will not be random and instead fully deterministic.**
185185
* `'clocks'`: Support for clocks and duration polls, depends on `wasi:clocks` and `wasi:io`. **When disabled, using any timer functions like setTimeout or setInterval will panic.**
186186
* `'http'`: Support for outbound HTTP via the `fetch` global in JS.
187+
* `'fetch-event'`: Support for `fetch` based incoming request handling (i.e. `addEventListener('fetch', ...)`)
187188

188-
Setting `disableFeatures: ['random', 'stdio', 'clocks', 'http']` will disable all features creating a minimal "pure component", that does not depend on any WASI APIs at all and just the target world.
189+
Setting `disableFeatures: ['random', 'stdio', 'clocks', 'http', 'fetch-event']` will disable all features creating a minimal "pure component", that does not depend on any WASI APIs at all and just the target world.
189190

190191
Note that pure components **will not report errors and will instead trap**, so that this should only be enabled after very careful testing.
191192

192193
Note that features explicitly imported by the target world cannot be disabled - if you target a component to a world that imports `wasi:clocks`, then `disableFeatures: ['clocks']` will not be supported.
193194

195+
Note that depending on your component implementation, some features may be automatically disabled. For example, if using
196+
`wasi:http/incoming-handler` manually, the `fetch-event` cannot be used.
197+
194198
## Using StarlingMonkey's `fetch-event`
195199

196-
The StarlingMonkey engine provides the ability to use `fetchEvent` to handle calls to `wasi:http/incoming-handler@0.2.0#handle`. When targeting worlds that export `wasi:http/incoming-handler@0.2.0` the fetch event will automatically be attached. Alternatively, to override the fetch event with a custom handler, export an explicit `incomingHandler` or `'wasi:http/incoming-handler@0.2.0'` object. Using the `fetchEvent` requires enabling the `http` feature.
200+
The StarlingMonkey engine provides the ability to use `fetchEvent` to handle calls to `wasi:http/incoming-handler@0.2.0#handle`.
201+
202+
When targeting worlds that export `wasi:http/incoming-handler@0.2.0` the fetch event will automatically be attached. Alternatively,
203+
to override the fetch event with a custom handler, export an explicit `incomingHandler` or `'wasi:http/incoming-handler@0.2.0'`
204+
object. Using the `fetchEvent` requires enabling the `http` feature.
205+
206+
> [!WARNING]
207+
> If using `fetch-event`, ensure that you *do not* manually import (i.e. exporting `incomingHandler` from your ES module).
208+
>
209+
> Modules that export `incomingHandler` and have the `http` feature enabled are assumed to be using `wasi:http` manually.
197210
198211
## API
199212

@@ -206,7 +219,7 @@ export function componentize(opts: {
206219
debugBuild?: bool,
207220
engine?: string,
208221
preview2Adapter?: string,
209-
disableFeatures?: ('stdio' | 'random' | 'clocks' | 'http')[],
222+
disableFeatures?: ('stdio' | 'random' | 'clocks' | 'http', 'fetch-event')[],
210223
}): {
211224
component: Uint8Array,
212225
imports: string[]

crates/spidermonkey-embedding-splicer/src/bin/splicer.rs

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
1-
use anyhow::{Context, Result};
2-
use clap::{Parser, Subcommand};
31
use std::fs;
42
use std::path::PathBuf;
3+
use std::str::FromStr;
54

6-
use spidermonkey_embedding_splicer::wit::Features;
5+
use anyhow::{Context, Result};
6+
use clap::{Parser, Subcommand};
7+
8+
use spidermonkey_embedding_splicer::wit::exports::local::spidermonkey_embedding_splicer::splicer::Features;
79
use spidermonkey_embedding_splicer::{splice, stub_wasi};
810

911
#[derive(Parser, Debug)]
@@ -48,6 +50,10 @@ enum Commands {
4850
#[arg(short, long)]
4951
out_dir: PathBuf,
5052

53+
/// Features to enable (multiple allowed)
54+
#[arg(short, long)]
55+
features: Vec<String>,
56+
5157
/// Path to WIT file or directory
5258
#[arg(long)]
5359
wit_path: Option<PathBuf>,
@@ -69,18 +75,13 @@ enum Commands {
6975
/// clocks,
7076
/// random,
7177
/// http,
78+
/// fetch-event,
7279
///}
73-
fn map_features(features: &Vec<String>) -> Vec<Features> {
80+
fn map_features(features: &Vec<String>) -> Result<Vec<Features>> {
7481
features
7582
.iter()
76-
.map(|f| match f.as_str() {
77-
"stdio" => Features::Stdio,
78-
"clocks" => Features::Clocks,
79-
"random" => Features::Random,
80-
"http" => Features::Http,
81-
_ => panic!("Unknown feature: {}", f),
82-
})
83-
.collect()
83+
.map(|f| Features::from_str(f.as_str()))
84+
.collect::<Result<Vec<Features>>>()
8485
}
8586

8687
fn main() -> Result<()> {
@@ -98,7 +99,7 @@ fn main() -> Result<()> {
9899
.with_context(|| format!("Failed to read input file: {}", input.display()))?;
99100

100101
let wit_path_str = wit_path.as_ref().map(|p| p.to_string_lossy().to_string());
101-
let features = map_features(&features);
102+
let features = map_features(&features)?;
102103

103104
let result = stub_wasi::stub_wasi(wasm, features, None, wit_path_str, world_name)
104105
.map_err(|e| anyhow::anyhow!(e))?;
@@ -115,6 +116,7 @@ fn main() -> Result<()> {
115116
Commands::SpliceBindings {
116117
input,
117118
out_dir,
119+
features,
118120
wit_path,
119121
world_name,
120122
debug,
@@ -129,8 +131,12 @@ fn main() -> Result<()> {
129131

130132
let wit_path_str = wit_path.as_ref().map(|p| p.to_string_lossy().to_string());
131133

132-
let result = splice::splice_bindings(engine, world_name, wit_path_str, None, debug)
133-
.map_err(|e| anyhow::anyhow!(e))?;
134+
let features = map_features(&features)?;
135+
136+
let result =
137+
splice::splice_bindings(engine, features, world_name, wit_path_str, None, debug)
138+
.map_err(|e| anyhow::anyhow!(e))?;
139+
134140
fs::write(&out_dir.join("component.wasm"), result.wasm).with_context(|| {
135141
format!(
136142
"Failed to write output file: {}",

crates/spidermonkey-embedding-splicer/src/bindgen.rs

Lines changed: 22 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ use wit_component::StringEncoding;
1919
use wit_parser::abi::WasmType;
2020
use wit_parser::abi::{AbiVariant, WasmSignature};
2121

22+
use crate::wit::exports::local::spidermonkey_embedding_splicer::splicer::Features;
23+
2224
use crate::{uwrite, uwriteln};
2325

2426
#[derive(Debug)]
@@ -104,6 +106,9 @@ struct JsBindgen<'a> {
104106
resource_directions: HashMap<TypeId, AbiVariant>,
105107

106108
imported_resources: BTreeSet<TypeId>,
109+
110+
/// Features that were enabled at the time of generation
111+
features: &'a Vec<Features>,
107112
}
108113

109114
#[derive(Debug)]
@@ -131,7 +136,11 @@ pub struct Componentization {
131136
pub resource_imports: Vec<(String, String, u32)>,
132137
}
133138

134-
pub fn componentize_bindgen(resolve: &Resolve, wid: WorldId) -> Result<Componentization> {
139+
pub fn componentize_bindgen(
140+
resolve: &Resolve,
141+
wid: WorldId,
142+
features: &Vec<Features>,
143+
) -> Result<Componentization> {
135144
let mut bindgen = JsBindgen {
136145
src: Source::default(),
137146
esm_bindgen: EsmBindgen::default(),
@@ -146,6 +155,7 @@ pub fn componentize_bindgen(resolve: &Resolve, wid: WorldId) -> Result<Component
146155
imports: Vec::new(),
147156
resource_directions: HashMap::new(),
148157
imported_resources: BTreeSet::new(),
158+
features,
149159
};
150160

151161
bindgen.sizes.fill(resolve);
@@ -393,57 +403,19 @@ impl JsBindgen<'_> {
393403
return intrinsic.name().to_string();
394404
}
395405

396-
fn exports_bindgen(
397-
&mut self,
398-
// guest_exports: &Option<Vec<String>>,
399-
// features: Vec<Features>,
400-
) -> Result<()> {
406+
fn exports_bindgen(&mut self) -> Result<()> {
401407
for (key, export) in &self.resolve.worlds[self.world].exports {
402408
let name = self.resolve.name_world_key(key);
403-
// Do not generate exports when the guest export is not implemented.
404-
// We check both the full interface name - "ns:pkg@v/my-interface" and the
405-
// aliased interface name "myInterface". All other names are always
406-
// camel-case in the check.
407-
// match key {
408-
// WorldKey::Interface(iface) => {
409-
// if !guest_exports.contains(&name) {
410-
// let iface = &self.resolve.interfaces[*iface];
411-
// if let Some(iface_name) = iface.name.as_ref() {
412-
// let camel_case_name = iface_name.to_lower_camel_case();
413-
// if !guest_exports.contains(&camel_case_name) {
414-
// // For wasi:http/incoming-handler, we treat it
415-
// // as a special case as the engine already
416-
// // provides the export using fetchEvent and that
417-
// // can be used when an explicit export is not
418-
// // defined by the guest content.
419-
// if iface_name == "incoming-handler"
420-
// || name.starts_with("wasi:http/incoming-handler@0.2.")
421-
// {
422-
// if !features.contains(&Features::Http) {
423-
// bail!(
424-
// "JS export definition for '{}' not found. Cannot use fetchEvent because the http feature is not enabled.",
425-
// camel_case_name
426-
// )
427-
// }
428-
// continue;
429-
// }
430-
// bail!("Expected a JS export definition for '{}'", camel_case_name);
431-
// }
432-
// // TODO: move populate_export_aliases to a preprocessing
433-
// // step that doesn't require esm_bindgen, so that we can
434-
// // do alias deduping here as well.
435-
// } else {
436-
// continue;
437-
// }
438-
// }
439-
// }
440-
// WorldKey::Name(export_name) => {
441-
// let camel_case_name = export_name.to_lower_camel_case();
442-
// if !guest_exports.contains(&camel_case_name) {
443-
// bail!("Expected a JS export definition for '{}'", camel_case_name);
444-
// }
445-
// }
446-
// }
409+
410+
// Skip bindings generation for wasi:http/incoming-handler if the fetch-event
411+
// feature was enabled. We expect that the built-in engine implementation will be used
412+
if name.starts_with("wasi:http/incoming-handler@0.2.")
413+
&& self.features.contains(&Features::FetchEvent)
414+
{
415+
continue;
416+
}
417+
418+
// TODO: check if the export is detected
447419

448420
match export {
449421
WorldItem::Function(func) => {

crates/spidermonkey-embedding-splicer/src/lib.rs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,14 @@
1-
use anyhow::{bail, Context, Result};
21
use std::path::Path;
32

3+
use anyhow::{bail, Context, Result};
4+
use wit_parser::{PackageId, Resolve};
5+
46
pub mod bindgen;
57
pub mod splice;
68
pub mod stub_wasi;
9+
pub mod wit;
710

8-
use crate::wit::{CoreFn, CoreTy};
9-
use wit_parser::{PackageId, Resolve};
10-
11-
pub mod wit {
12-
wit_bindgen::generate!({
13-
world: "spidermonkey-embedding-splicer",
14-
pub_export_macro: true
15-
});
16-
}
11+
use wit::exports::local::spidermonkey_embedding_splicer::splicer::{CoreFn, CoreTy};
1712

1813
/// Calls [`write!`] with the passed arguments and unwraps the result.
1914
///

crates/spidermonkey-embedding-splicer/src/splice.rs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
use crate::bindgen::BindingItem;
2-
use crate::wit::{CoreFn, CoreTy, SpliceResult};
3-
use crate::{bindgen, map_core_fn, parse_wit, splice};
1+
use std::path::PathBuf;
2+
43
use anyhow::Result;
54
use orca_wasm::ir::function::{FunctionBuilder, FunctionModifier};
65
use orca_wasm::ir::id::{ExportsID, FunctionID, LocalID};
@@ -9,7 +8,6 @@ use orca_wasm::ir::types::{BlockType, ElementItems, InstrumentationMode};
98
use orca_wasm::module_builder::AddLocal;
109
use orca_wasm::opcode::{Inject, InjectAt};
1110
use orca_wasm::{DataType, Opcode};
12-
use std::path::PathBuf;
1311
use wasm_encoder::{Encode, Section};
1412
use wasmparser::ExternalKind;
1513
use wasmparser::MemArg;
@@ -18,6 +16,12 @@ use wit_component::metadata::{decode, Bindgen};
1816
use wit_component::StringEncoding;
1917
use wit_parser::Resolve;
2018

19+
use crate::bindgen::BindingItem;
20+
use crate::wit::exports::local::spidermonkey_embedding_splicer::splicer::{
21+
CoreFn, CoreTy, Features, SpliceResult,
22+
};
23+
use crate::{bindgen, map_core_fn, parse_wit, splice};
24+
2125
// Returns
2226
// pub struct SpliceResult {
2327
// pub wasm: _rt::Vec::<u8>,
@@ -28,6 +32,7 @@ use wit_parser::Resolve;
2832
// }
2933
pub fn splice_bindings(
3034
engine: Vec<u8>,
35+
features: Vec<Features>,
3136
world_name: Option<String>,
3237
wit_path: Option<String>,
3338
wit_source: Option<String>,
@@ -109,7 +114,7 @@ pub fn splice_bindings(
109114
};
110115

111116
let componentized =
112-
bindgen::componentize_bindgen(&resolve, world).map_err(|err| err.to_string())?;
117+
bindgen::componentize_bindgen(&resolve, world, &features).map_err(|err| err.to_string())?;
113118

114119
resolve
115120
.merge_worlds(engine_world, world)
@@ -251,8 +256,8 @@ pub fn splice_bindings(
251256
));
252257
}
253258

254-
let mut wasm =
255-
splice::splice(engine, imports, exports, debug).map_err(|e| format!("{:?}", e))?;
259+
let mut wasm = splice::splice(engine, imports, exports, features, debug)
260+
.map_err(|e| format!("{:?}", e))?;
256261

257262
// add the world section to the spliced wasm
258263
wasm.push(section.id());
@@ -334,19 +339,25 @@ pub fn splice(
334339
engine: Vec<u8>,
335340
imports: Vec<(String, String, CoreFn, Option<i32>)>,
336341
exports: Vec<(String, CoreFn)>,
342+
features: Vec<Features>,
337343
debug: bool,
338344
) -> Result<Vec<u8>> {
339345
let mut module = Module::parse(&*engine, false).unwrap();
340346

341347
// since StarlingMonkey implements CLI Run and incoming handler,
342348
// we override them only if the guest content exports those functions
343349
remove_if_exported_by_js(&mut module, &exports, "wasi:cli/run@0.2.", "#run");
344-
remove_if_exported_by_js(
345-
&mut module,
346-
&exports,
347-
"wasi:http/incoming-handler@0.2.",
348-
"#handle",
349-
);
350+
351+
// if 'fetch-event' feature is disabled (default being default-enabled),
352+
// remove the built-in incoming-handler which is built around it's use.
353+
if !features.contains(&Features::FetchEvent) {
354+
remove_if_exported_by_js(
355+
&mut module,
356+
&exports,
357+
"wasi:http/incoming-handler@0.2.",
358+
"#handle",
359+
);
360+
}
350361

351362
// we reencode the WASI world component data, so strip it out from the
352363
// custom section

crates/spidermonkey-embedding-splicer/src/stub_wasi.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
use crate::parse_wit;
2-
use crate::wit::Features;
1+
use std::collections::HashSet;
2+
use std::path::PathBuf;
3+
use std::time::{SystemTime, UNIX_EPOCH};
4+
35
use anyhow::{bail, Result};
46
use orca_wasm::ir::function::FunctionBuilder;
57
use orca_wasm::ir::id::{FunctionID, LocalID};
68
use orca_wasm::ir::module::module_functions::FuncKind;
79
use orca_wasm::ir::types::{BlockType, InitExpr, Value};
810
use orca_wasm::module_builder::AddLocal;
911
use orca_wasm::{DataType, Instructions, Module, Opcode};
10-
use std::{
11-
collections::HashSet,
12-
path::PathBuf,
13-
time::{SystemTime, UNIX_EPOCH},
14-
};
1512
use wasmparser::{MemArg, TypeRef};
1613
use wit_parser::Resolve;
1714

15+
use crate::parse_wit;
16+
use crate::wit::exports::local::spidermonkey_embedding_splicer::splicer::Features;
17+
1818
const WASI_VERSIONS: [&str; 4] = ["0.2.0", "0.2.1", "0.2.2", "0.2.3"];
1919

2020
fn stub_wasi_imports<StubFn>(
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
use anyhow::{bail, Result};
2+
3+
wit_bindgen::generate!({
4+
world: "spidermonkey-embedding-splicer",
5+
pub_export_macro: true
6+
});
7+
8+
use crate::wit::exports::local::spidermonkey_embedding_splicer::splicer::Features;
9+
10+
impl std::str::FromStr for Features {
11+
type Err = anyhow::Error;
12+
13+
fn from_str(s: &str) -> Result<Self> {
14+
match s {
15+
"stdio" => Ok(Features::Stdio),
16+
"clocks" => Ok(Features::Clocks),
17+
"random" => Ok(Features::Random),
18+
"http" => Ok(Features::Http),
19+
_ => bail!("unrecognized feature string [{s}]"),
20+
}
21+
}
22+
}

0 commit comments

Comments
 (0)