Skip to content

Commit

Permalink
Lift http handler type discovery up a layer
Browse files Browse the repository at this point in the history
This commit lifts the discovery of what type of handler a component is,
for example spin-vs-wasi-vs-wasi-snapshot, up one layer from where it
currently resides. Previously this determination was made on each
request and now it's made when a component first loaded and
pre-instantiated instead. This would shift possible errors earlier in
the system, for example.

This removes a bit of per-request work and makes the creation of the
handler in `execute_wasi` a bit more straightforward with less guessing
of exports for something that was already probed.

Signed-off-by: Alex Crichton <[email protected]>
  • Loading branch information
alexcrichton committed May 14, 2024
1 parent 5d09e10 commit b2dfbf0
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 59 deletions.
9 changes: 8 additions & 1 deletion crates/core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use self::host_component::{HostComponents, HostComponentsBuilder};
pub use async_trait::async_trait;
pub use wasmtime::{
self,
component::{Component, Instance},
component::{types::Component as ComponentType, Component, Instance},
Instance as ModuleInstance, Module, Trap,
};
pub use wasmtime_wasi::preview2::I32Exit;
Expand Down Expand Up @@ -462,6 +462,13 @@ impl<T: OutboundWasiHttpHandler + Send + Sync> Engine<T> {
Ok(InstancePre { inner })
}

/// Returns the type of this component to reflect on it at runtime.
pub fn component_type(&self, component: &Component) -> Result<ComponentType> {
// NB: With Wasmtime 20 and bytecodealliance/wasmtime#8078 this method
// should probably be removed to just use that instead.
self.linker.substituted_component_type(component)
}

/// Creates a new [`ModuleInstancePre`] for the given [`Module`].
#[instrument(skip_all, level = "debug")]
pub fn module_instantiate_pre(&self, module: &Module) -> Result<ModuleInstancePre<T>> {
Expand Down
120 changes: 70 additions & 50 deletions crates/trigger-http/src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use std::{net::SocketAddr, str, str::FromStr};

use crate::{Body, ChainedRequestHandler, HttpExecutor, HttpInstance, HttpTrigger, Store};
use anyhow::bail;
use anyhow::{anyhow, Context, Result};
use futures::TryFutureExt;
use http::{HeaderName, HeaderValue};
Expand All @@ -11,7 +10,7 @@ use outbound_http::OutboundHttpComponent;
use spin_core::async_trait;
use spin_core::wasi_2023_10_18::exports::wasi::http::incoming_handler::Guest as IncomingHandler2023_10_18;
use spin_core::wasi_2023_11_10::exports::wasi::http::incoming_handler::Guest as IncomingHandler2023_11_10;
use spin_core::Instance;
use spin_core::{ComponentType, Instance};
use spin_http::body;
use spin_http::routes::RouteMatch;
use spin_trigger::TriggerAppEngine;
Expand Down Expand Up @@ -43,26 +42,21 @@ impl HttpExecutor for HttpHandlerExecutor {
);

let (instance, mut store) = engine.prepare_instance(component_id).await?;
let HttpInstance::Component(instance) = instance else {
let HttpInstance::Component(instance, ty) = instance else {
unreachable!()
};

set_http_origin_from_request(&mut store, engine.clone(), self, &req);

let resp = match HandlerType::from_exports(instance.exports(&mut store)) {
Some(HandlerType::Wasi) => {
Self::execute_wasi(store, instance, base, route_match, req, client_addr).await?
}
Some(HandlerType::Spin) => {
let resp = match ty {
HandlerType::Spin => {
Self::execute_spin(store, instance, base, route_match, req, client_addr)
.await
.map_err(contextualise_err)?
}
None => bail!(
"Expected component to either export `{WASI_HTTP_EXPORT_2023_10_18}`, \
`{WASI_HTTP_EXPORT_2023_11_10}`, `{WASI_HTTP_EXPORT_0_2_0}`, \
or `fermyon:spin/inbound-http` but it exported none of those"
),
_ => {
Self::execute_wasi(store, instance, ty, base, route_match, req, client_addr).await?
}
};

tracing::info!(
Expand Down Expand Up @@ -157,6 +151,7 @@ impl HttpHandlerExecutor {
async fn execute_wasi(
mut store: Store,
instance: Instance,
ty: HandlerType,
base: &str,
route_match: &RouteMatch,
mut req: Request<Body>,
Expand Down Expand Up @@ -188,31 +183,33 @@ impl HttpHandlerExecutor {
Handler2023_10_18(IncomingHandler2023_10_18),
}

let handler = match instance
.exports(&mut store)
.instance("wasi:http/[email protected]")
{
Some(mut instance) => Some(Handler::Handler2023_10_18(IncomingHandler2023_10_18::new(
&mut instance,
)?)),
None => None,
};
let handler = match handler {
Some(handler) => Some(handler),
None => match instance
.exports(&mut store)
.instance("wasi:http/[email protected]")
let handler =
{
Some(mut instance) => Some(Handler::Handler2023_11_10(
IncomingHandler2023_11_10::new(&mut instance)?,
)),
None => None,
},
};
let handler = match handler {
Some(handler) => handler,
None => Handler::Latest(Proxy::new(&mut store, &instance)?),
};
let mut exports = instance.exports(&mut store);
match ty {
HandlerType::Wasi2023_10_18 => {
let mut instance = exports
.instance(WASI_HTTP_EXPORT_2023_10_18)
.ok_or_else(|| {
anyhow!("export of `{WASI_HTTP_EXPORT_2023_10_18}` not an instance")
})?;
Handler::Handler2023_10_18(IncomingHandler2023_10_18::new(&mut instance)?)
}
HandlerType::Wasi2023_11_10 => {
let mut instance = exports
.instance(WASI_HTTP_EXPORT_2023_11_10)
.ok_or_else(|| {
anyhow!("export of `{WASI_HTTP_EXPORT_2023_11_10}` not an instance")
})?;
Handler::Handler2023_11_10(IncomingHandler2023_11_10::new(&mut instance)?)
}
HandlerType::Wasi0_2 => {
drop(exports);
Handler::Latest(Proxy::new(&mut store, &instance)?)
}
HandlerType::Spin => unreachable!(),
}
};

let span = tracing::debug_span!("execute_wasi");
let handle = task::spawn(
Expand Down Expand Up @@ -336,28 +333,51 @@ impl HttpHandlerExecutor {
}

/// Whether this handler uses the custom Spin http handler interface for wasi-http
enum HandlerType {
#[derive(Copy, Clone)]
pub enum HandlerType {
Spin,
Wasi,
Wasi0_2,
Wasi2023_11_10,
Wasi2023_10_18,
}

const WASI_HTTP_EXPORT_2023_10_18: &str = "wasi:http/[email protected]";
const WASI_HTTP_EXPORT_2023_11_10: &str = "wasi:http/[email protected]";
const WASI_HTTP_EXPORT_0_2_0: &str = "wasi:http/[email protected]";

impl HandlerType {
/// Determine the handler type from the exports
fn from_exports(mut exports: wasmtime::component::Exports<'_>) -> Option<HandlerType> {
if exports.instance(WASI_HTTP_EXPORT_2023_10_18).is_some()
|| exports.instance(WASI_HTTP_EXPORT_2023_11_10).is_some()
|| exports.instance(WASI_HTTP_EXPORT_0_2_0).is_some()
{
return Some(HandlerType::Wasi);
}
if exports.instance("fermyon:spin/inbound-http").is_some() {
return Some(HandlerType::Spin);
/// Determine the handler type from the exports of a component
pub fn from_component(ty: &ComponentType) -> Result<HandlerType> {
let mut handler_ty = None;

let mut set = |ty: HandlerType| {
if handler_ty.is_none() {
handler_ty = Some(ty);
Ok(())
} else {
Err(anyhow!(
"component exports multiple different handlers but \
it's expected to export only one"
))
}
};
for (name, _) in ty.exports() {
match name {
WASI_HTTP_EXPORT_2023_10_18 => set(HandlerType::Wasi2023_10_18)?,
WASI_HTTP_EXPORT_2023_11_10 => set(HandlerType::Wasi2023_11_10)?,
WASI_HTTP_EXPORT_0_2_0 => set(HandlerType::Wasi0_2)?,
"fermyon:spin/inbound-http" => set(HandlerType::Spin)?,
_ => {}
}
}
None

handler_ty.ok_or_else(|| {
anyhow!(
"Expected component to either export `{WASI_HTTP_EXPORT_2023_10_18}`, \
`{WASI_HTTP_EXPORT_2023_11_10}`, `{WASI_HTTP_EXPORT_0_2_0}`, \
or `fermyon:spin/inbound-http` but it exported none of those"
)
})
}
}

Expand Down
21 changes: 13 additions & 8 deletions crates/trigger-http/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use wasmtime_wasi_http::{
};

use crate::{
handler::HttpHandlerExecutor,
handler::{HandlerType, HttpHandlerExecutor},
instrument::{instrument_error, MatchedRoute},
wagi::WagiHttpExecutor,
};
Expand Down Expand Up @@ -101,12 +101,12 @@ impl CliArgs {
}

pub enum HttpInstancePre {
Component(spin_core::InstancePre<RuntimeData>),
Component(spin_core::InstancePre<RuntimeData>, HandlerType),
Module(spin_core::ModuleInstancePre<RuntimeData>),
}

pub enum HttpInstance {
Component(spin_core::Instance),
Component(spin_core::Instance, HandlerType),
Module(spin_core::ModuleInstance),
}

Expand Down Expand Up @@ -204,16 +204,21 @@ impl TriggerInstancePre<RuntimeData, HttpTriggerConfig> for HttpInstancePre {
))
} else {
let comp = component.load_component(engine).await?;
Ok(HttpInstancePre::Component(engine.instantiate_pre(&comp)?))
let ty = engine.component_type(&comp)?;
let handler_ty = HandlerType::from_component(&ty)?;
Ok(HttpInstancePre::Component(
engine.instantiate_pre(&comp)?,
handler_ty,
))
}
}

async fn instantiate(&self, store: &mut Store) -> Result<HttpInstance> {
match self {
HttpInstancePre::Component(pre) => pre
.instantiate_async(store)
.await
.map(HttpInstance::Component),
HttpInstancePre::Component(pre, ty) => Ok(HttpInstance::Component(
pre.instantiate_async(store).await?,
*ty,
)),
HttpInstancePre::Module(pre) => {
pre.instantiate_async(store).await.map(HttpInstance::Module)
}
Expand Down

0 comments on commit b2dfbf0

Please sign in to comment.