Skip to content
Draft
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
51 changes: 33 additions & 18 deletions crates/factors-executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,12 +203,12 @@ impl<T: RuntimeFactors, U: Send + 'static> FactorsExecutorApp<T, U> {
///
/// It is generic over the executor's [`RuntimeFactors`] and any ad-hoc additional
/// per-instance state needed by the caller.
pub struct FactorsInstanceBuilder<'a, F: RuntimeFactors, U: 'static> {
pub struct FactorsInstanceBuilder<'a, T: RuntimeFactors, U: 'static> {
app_component: AppComponent<'a>,
store_builder: spin_core::StoreBuilder,
factor_builders: F::InstanceBuilders,
instance_pre: &'a InstancePre<F, U>,
factors: &'a F,
factor_builders: T::InstanceBuilders,
instance_pre: &'a InstancePre<T, U>,
factors: &'a T,
}

impl<T: RuntimeFactors, U: 'static> FactorsInstanceBuilder<'_, T, U> {
Expand Down Expand Up @@ -244,24 +244,21 @@ impl<T: RuntimeFactors, U: 'static> FactorsInstanceBuilder<'_, T, U> {
}

impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
/// Instantiates the instance with the given executor instance state
/// Instantiates the instance with the given executor instance state.
pub async fn instantiate(
self,
executor_instance_state: U,
) -> anyhow::Result<(
spin_core::Instance,
spin_core::Store<InstanceState<T::InstanceState, U>>,
)> {
let instance_state = InstanceState {
core: Default::default(),
factors: self.factors.build_instance_state(self.factor_builders)?,
executor: executor_instance_state,
cpu_time_elapsed: Duration::from_millis(0),
cpu_time_last_entry: None,
memory_used_on_init: 0,
component_id: self.app_component.id().into(),
};
let mut store = self.store_builder.build(instance_state)?;
let mut store = Self::build_store(
self.store_builder,
self.factor_builders,
&self.app_component,
self.factors,
executor_instance_state,
)?;

#[cfg(feature = "cpu-time-metrics")]
store.as_mut().call_hook(|mut store, hook| {
Expand All @@ -277,20 +274,38 @@ impl<T: RuntimeFactors, U: Send> FactorsInstanceBuilder<'_, T, U> {
Ok((instance, store))
}

/// Builds a store with the given executor instance state.
pub fn instantiate_store(
self,
executor_instance_state: U,
) -> anyhow::Result<spin_core::Store<InstanceState<T::InstanceState, U>>> {
Self::build_store(
self.store_builder,
self.factor_builders,
&self.app_component,
self.factors,
executor_instance_state,
)
}

fn build_store(
store_builder: spin_core::StoreBuilder,
factor_builders: T::InstanceBuilders,
app_component: &AppComponent,
factors: &T,
executor_instance_state: U,
) -> anyhow::Result<spin_core::Store<InstanceState<T::InstanceState, U>>> {
let factors = factors.build_instance_state(factor_builders)?;
let instance_state = InstanceState {
core: Default::default(),
factors: self.factors.build_instance_state(self.factor_builders)?,
factors,
executor: executor_instance_state,
cpu_time_elapsed: Duration::from_millis(0),
cpu_time_last_entry: None,
memory_used_on_init: 0,
component_id: self.app_component.id().into(),
component_id: app_component.id().into(),
};
self.store_builder.build(instance_state)
store_builder.build(instance_state)
}
}

Expand Down
37 changes: 19 additions & 18 deletions crates/http/src/trigger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,32 @@ pub fn default_base() -> String {
}

/// The type of http handler export used by a component.
// Note: These are ordered by descending newness, reflecting the selection order
// in from_instance_pre below.
pub enum HandlerType<S: HandlerState> {
Spin,
Wagi(CommandIndices),
Wasi0_2(ProxyIndices),
Wasi0_3(ServiceIndices, ProxyHandler<S>),
Wasi0_2(ProxyIndices),
Wasi2023_11_10(ProxyIndices2023_11_10),
Wasi2023_10_18(ProxyIndices2023_10_18),
Spin,
Wagi(CommandIndices),
}

/// The `incoming-handler` export for `wasi:http` version rc-2023-10-18
const WASI_HTTP_EXPORT_2023_10_18: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-10-18";
/// The `incoming-handler` export for `wasi:http` version rc-2023-11-10
const WASI_HTTP_EXPORT_2023_11_10: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-11-10";
/// The `incoming-handler` export prefix for all `wasi:http` 0.2 versions
const WASI_HTTP_EXPORT_0_2_PREFIX: &str = "wasi:http/incoming-handler@0.2";
/// The `handler` export `wasi:http` version 0.3.0-rc-2025-08-15
const WASI_HTTP_EXPORT_0_3_0_RC_03_15: &str = "wasi:http/handler@0.3.0-rc-2026-03-15";
/// The `incoming-handler` export prefix for all `wasi:http` 0.2 versions
const WASI_HTTP_EXPORT_0_2_PREFIX: &str = "wasi:http/incoming-handler@0.2";
/// The `incoming-handler` export for `wasi:http` version rc-2023-11-10
const WASI_HTTP_EXPORT_2023_11_10: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-11-10";
/// The `incoming-handler` export for `wasi:http` version rc-2023-10-18
const WASI_HTTP_EXPORT_2023_10_18: &str = "wasi:http/incoming-handler@0.2.0-rc-2023-10-18";
/// The `inbound-http` export for `fermyon:spin`
const SPIN_HTTP_EXPORT: &str = "fermyon:spin/inbound-http";

impl<T, S: HandlerState<StoreData = T>> HandlerType<S> {
/// Determine the handler type from the exports of a component.
pub fn from_instance_pre(pre: &InstancePre<T>, handler_state: S) -> anyhow::Result<Self> {
let mut candidates = Vec::new();
if let Ok(indices) = ProxyIndices::new(pre) {
candidates.push(HandlerType::Wasi0_2(indices));
}
if let Ok(pre) = ServicePre::new(pre.clone()) {
candidates.push(HandlerType::Wasi0_3(
// We `.unwrap()` here because the `Ok(_)` result from
Expand All @@ -56,12 +55,15 @@ impl<T, S: HandlerState<StoreData = T>> HandlerType<S> {
ProxyHandler::new(handler_state, ProxyPre::P3(pre)),
));
}
if let Ok(indices) = ProxyIndices2023_10_18::new(pre) {
candidates.push(HandlerType::Wasi2023_10_18(indices));
if let Ok(indices) = ProxyIndices::new(pre) {
candidates.push(HandlerType::Wasi0_2(indices));
}
if let Ok(indices) = ProxyIndices2023_11_10::new(pre) {
candidates.push(HandlerType::Wasi2023_11_10(indices));
}
if let Ok(indices) = ProxyIndices2023_10_18::new(pre) {
candidates.push(HandlerType::Wasi2023_10_18(indices));
}
if pre
.component()
.get_export_index(None, SPIN_HTTP_EXPORT)
Expand All @@ -71,6 +73,7 @@ impl<T, S: HandlerState<StoreData = T>> HandlerType<S> {
}

match candidates.len() {
1 => Ok(candidates.pop().unwrap()),
0 => {
anyhow::bail!(
"Expected component to export one of \
Expand All @@ -83,10 +86,8 @@ impl<T, S: HandlerState<StoreData = T>> HandlerType<S> {
If you're sure this is an HTTP module, check if a Spin upgrade is available: this may handle the newer version."
)
}
1 => Ok(candidates.pop().unwrap()),
_ => anyhow::bail!(
"component exports multiple different handlers but \
it's expected to export only one"
too_many => anyhow::bail!(
"component exports {too_many} different handlers but it's expected to export only one"
),
}
}
Expand Down
95 changes: 51 additions & 44 deletions crates/trigger-http/src/server.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::{
collections::HashMap,
future::Future,
io::{ErrorKind, IsTerminal},
net::SocketAddr,
sync::{Arc, OnceLock},
Expand Down Expand Up @@ -180,9 +179,10 @@ impl<F: RuntimeFactors> HttpServer<F> {
None | Some(HttpExecutorType::Http) => HandlerType::from_instance_pre(
pre,
HttpHandlerState {
trigger_app: trigger_app.clone(),
component_id: component_id.into(),
reuse_config,
server: Default::default(),
self_scheme: Default::default(),
},
)?,
Some(HttpExecutorType::Wagi(wagi_config)) => {
Expand Down Expand Up @@ -329,7 +329,7 @@ impl<F: RuntimeFactors> HttpServer<F> {
server_scheme: Scheme,
client_addr: SocketAddr,
) -> anyhow::Result<Response<Body>> {
set_req_uri(&mut req, server_scheme.clone())?;
set_req_uri(&mut req, server_scheme)?;
let app_id = self
.trigger_app
.app()
Expand All @@ -355,7 +355,6 @@ impl<F: RuntimeFactors> HttpServer<F> {
self.respond_wasm_component(
req,
route_match,
server_scheme,
client_addr,
component,
&trigger_config.executor,
Expand Down Expand Up @@ -383,28 +382,10 @@ impl<F: RuntimeFactors> HttpServer<F> {
self: &Arc<Self>,
req: Request<Body>,
route_match: RouteMatch<'_, '_>,
server_scheme: Scheme,
client_addr: SocketAddr,
component_id: &str,
executor: &Option<HttpExecutorType>,
) -> anyhow::Result<Response<Body>> {
let mut instance_builder = self.trigger_app.prepare(component_id)?;

// Set up outbound HTTP request origin and service chaining
// The outbound HTTP factor is required since both inbound and outbound wasi HTTP
// implementations assume they use the same underlying wasmtime resource storage.
// Eventually, we may be able to factor this out to a separate factor.
let outbound_http = instance_builder
.factor_builder::<OutboundHttpFactor>()
.context(
"The wasi HTTP trigger was configured without the required wasi outbound http support",
)?;

let self_addr = self.get_local_addr();
let origin = SelfRequestOrigin::create(server_scheme, &self_addr.to_string())?;
outbound_http.set_self_request_origin(origin);
outbound_http.set_request_interceptor(OutboundHttpInterceptor::new(self.clone()))?;

// Prepare HTTP executor
let handler_type = self
.component_handler_types
Expand All @@ -414,21 +395,21 @@ impl<F: RuntimeFactors> HttpServer<F> {

let res = match executor {
HttpExecutorType::Http => match handler_type {
HandlerType::Spin => {
SpinHttpExecutor
.execute(instance_builder, &route_match, req, client_addr)
.await
}
HandlerType::Wasi0_3(_, handler) => {
Wasip3HttpExecutor(handler)
.execute(&route_match, req, client_addr)
.execute(self, &route_match, req, client_addr)
.await
}
HandlerType::Wasi0_2(_)
| HandlerType::Wasi2023_11_10(_)
| HandlerType::Wasi2023_10_18(_) => {
WasiHttpExecutor { handler_type }
.execute(instance_builder, &route_match, req, client_addr)
.execute(self, &route_match, req, client_addr, component_id)
.await
}
HandlerType::Spin => {
SpinHttpExecutor
.execute(self, &route_match, req, client_addr, component_id)
.await
}
HandlerType::Wagi(_) => unreachable!(),
Expand All @@ -443,7 +424,7 @@ impl<F: RuntimeFactors> HttpServer<F> {
indices,
};
executor
.execute(instance_builder, &route_match, req, client_addr)
.execute(self, &route_match, req, client_addr, component_id)
.await
}
};
Expand All @@ -460,6 +441,31 @@ impl<F: RuntimeFactors> HttpServer<F> {
}
}

pub(crate) fn trigger_instance_builder(
self: &'_ Arc<Self>,
component_id: &str,
self_scheme: Option<&Scheme>,
) -> anyhow::Result<TriggerInstanceBuilder<'_, F>> {
let mut instance_builder = self.trigger_app.prepare(component_id)?;

// Set up outbound HTTP request origin and service chaining
// The outbound HTTP factor is required since both inbound and outbound wasi HTTP
// implementations assume they use the same underlying wasmtime resource storage.
// Eventually, we may be able to factor this out to a separate factor.
let outbound_http = instance_builder
.factor_builder::<OutboundHttpFactor>()
.context(
"The wasi HTTP trigger was configured without the required wasi outbound http support",
)?;

let self_scheme = self_scheme.cloned().unwrap_or(Scheme::HTTPS);
let self_addr = self.get_local_addr();
let origin = SelfRequestOrigin::create(self_scheme, &self_addr.to_string())?;
outbound_http.set_self_request_origin(origin);
outbound_http.set_request_interceptor(OutboundHttpInterceptor::new(self.clone()))?;
Ok(instance_builder)
}

fn respond_static_response(
sr: &spin_http::config::StaticResponse,
) -> anyhow::Result<Response<Body>> {
Expand Down Expand Up @@ -688,21 +694,20 @@ fn set_req_uri(req: &mut Request<Body>, scheme: Scheme) -> anyhow::Result<()> {
Ok(())
}

/// An HTTP executor.
pub(crate) trait HttpExecutor {
fn execute<F: RuntimeFactors>(
&self,
instance_builder: TriggerInstanceBuilder<F>,
route_match: &RouteMatch<'_, '_>,
req: Request<Body>,
client_addr: SocketAddr,
) -> impl Future<Output = anyhow::Result<Response<Body>>>;
}

pub(crate) struct HttpHandlerState<F: RuntimeFactors> {
trigger_app: Arc<TriggerApp<F>>,
component_id: String,
reuse_config: InstanceReuseConfig,
server: OnceLock<Arc<HttpServer<F>>>,
self_scheme: OnceLock<Scheme>,
}

impl<F: RuntimeFactors> HttpHandlerState<F> {
pub(crate) fn init_once(&self, server: &Arc<HttpServer<F>>, first_uri: &Uri) {
self.server.get_or_init(|| server.clone());
if let Some(scheme) = first_uri.scheme() {
self.self_scheme.get_or_init(|| scheme.clone());
}
}
}

impl<F: RuntimeFactors> HandlerState for HttpHandlerState<F> {
Expand All @@ -711,8 +716,10 @@ impl<F: RuntimeFactors> HandlerState for HttpHandlerState<F> {
fn new_store(&self, _req_id: Option<u64>) -> wasmtime::Result<StoreBundle<Self::StoreData>> {
Ok(StoreBundle {
store: self
.trigger_app
.prepare(&self.component_id)
.server
.get()
.expect("server should have been set")
.trigger_instance_builder(&self.component_id, self.self_scheme.get())
.to_wasmtime_result()?
.instantiate_store(())
.to_wasmtime_result()?
Expand Down
Loading
Loading