From 87337b5dd762fd156abfa3967add98d883de5c46 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Thu, 25 Jun 2026 09:35:42 -0400 Subject: [PATCH 1/2] trigger-http: Remove useless server::HttpExecutor trait --- crates/trigger-http/src/server.rs | 13 ------------- crates/trigger-http/src/spin.rs | 5 ++--- crates/trigger-http/src/wagi.rs | 6 +++--- crates/trigger-http/src/wasi.rs | 6 +++--- 4 files changed, 8 insertions(+), 22 deletions(-) diff --git a/crates/trigger-http/src/server.rs b/crates/trigger-http/src/server.rs index 69120b40de..8783890ccf 100644 --- a/crates/trigger-http/src/server.rs +++ b/crates/trigger-http/src/server.rs @@ -1,6 +1,5 @@ use std::{ collections::HashMap, - future::Future, io::{ErrorKind, IsTerminal}, net::SocketAddr, sync::{Arc, OnceLock}, @@ -46,7 +45,6 @@ use wasmtime_wasi_http::p2::body::HyperOutgoingBody; use crate::{ Body, InstanceReuseConfig, NotFoundRouteKind, OutputFormat, TlsConfig, TriggerApp, - TriggerInstanceBuilder, headers::strip_forbidden_headers, instrument::{MatchedRoute, finalize_http_span, http_span, instrument_error}, outbound_http::OutboundHttpInterceptor, @@ -688,17 +686,6 @@ fn set_req_uri(req: &mut Request, scheme: Scheme) -> anyhow::Result<()> { Ok(()) } -/// An HTTP executor. -pub(crate) trait HttpExecutor { - fn execute( - &self, - instance_builder: TriggerInstanceBuilder, - route_match: &RouteMatch<'_, '_>, - req: Request, - client_addr: SocketAddr, - ) -> impl Future>>; -} - pub(crate) struct HttpHandlerState { trigger_app: Arc>, component_id: String, diff --git a/crates/trigger-http/src/spin.rs b/crates/trigger-http/src/spin.rs index a2a8d02925..c2c01b4c9a 100644 --- a/crates/trigger-http/src/spin.rs +++ b/crates/trigger-http/src/spin.rs @@ -12,16 +12,15 @@ use tracing::{Level, instrument}; use crate::{ Body, TriggerInstanceBuilder, headers::{append_headers, prepare_request_headers}, - server::HttpExecutor, }; /// An [`HttpExecutor`] that uses the `fermyon:spin/inbound-http` interface. #[derive(Clone)] pub struct SpinHttpExecutor; -impl HttpExecutor for SpinHttpExecutor { +impl SpinHttpExecutor { #[instrument(name = "spin_trigger_http.execute_wasm", skip_all, err(level = Level::INFO), fields(otel.name = format!("execute_wasm_component {}", route_match.lookup_key().to_string())))] - async fn execute( + pub async fn execute( &self, instance_builder: TriggerInstanceBuilder<'_, F>, route_match: &RouteMatch<'_, '_>, diff --git a/crates/trigger-http/src/wagi.rs b/crates/trigger-http/src/wagi.rs index ee6dba5bc2..b118ff83a0 100644 --- a/crates/trigger-http/src/wagi.rs +++ b/crates/trigger-http/src/wagi.rs @@ -11,16 +11,16 @@ use wasmtime_wasi::p2::bindings::CommandIndices; use wasmtime_wasi::p2::pipe::MemoryOutputPipe; use wasmtime_wasi_http::p2::body::HyperIncomingBody as Body; -use crate::{TriggerInstanceBuilder, headers::compute_default_headers, server::HttpExecutor}; +use crate::{TriggerInstanceBuilder, headers::compute_default_headers}; pub struct WagiHttpExecutor<'a> { pub wagi_config: &'a WagiTriggerConfig, pub indices: &'a CommandIndices, } -impl HttpExecutor for WagiHttpExecutor<'_> { +impl WagiHttpExecutor<'_> { #[instrument(name = "spin_trigger_http.execute_wagi", skip_all, err(level = Level::INFO), fields(otel.name = format!("execute_wagi_component {}", route_match.lookup_key().to_string())))] - async fn execute( + pub async fn execute( &self, mut instance_builder: TriggerInstanceBuilder<'_, F>, route_match: &RouteMatch<'_, '_>, diff --git a/crates/trigger-http/src/wasi.rs b/crates/trigger-http/src/wasi.rs index ae8721983d..4cf74191be 100644 --- a/crates/trigger-http/src/wasi.rs +++ b/crates/trigger-http/src/wasi.rs @@ -16,7 +16,7 @@ use wasmtime_wasi_http::handler::HandlerState; use wasmtime_wasi_http::p2::bindings::http::types::Scheme; use wasmtime_wasi_http::p2::{bindings::Proxy, body::HyperIncomingBody as Body}; -use crate::{TriggerInstanceBuilder, headers::prepare_request_headers, server::HttpExecutor}; +use crate::{TriggerInstanceBuilder, headers::prepare_request_headers}; pub(super) fn prepare_request( route_match: &RouteMatch<'_, '_>, @@ -51,9 +51,9 @@ pub struct WasiHttpExecutor<'a, S: HandlerState> { pub handler_type: &'a HandlerType, } -impl HttpExecutor for WasiHttpExecutor<'_, S> { +impl WasiHttpExecutor<'_, S> { #[instrument(name = "spin_trigger_http.execute_wasm", skip_all, err(level = Level::INFO), fields(otel.name = format!("execute_wasm_component {}", route_match.lookup_key().to_string())))] - async fn execute( + pub async fn execute( &self, instance_builder: TriggerInstanceBuilder<'_, F>, route_match: &RouteMatch<'_, '_>, From 1ed6d2f6b3c7a3647914dbb205d968cf9ef83524 Mon Sep 17 00:00:00 2001 From: Lann Martin Date: Thu, 25 Jun 2026 16:02:07 -0400 Subject: [PATCH 2/2] trigger-http: Fix service chaining for wasip3 handlers --- crates/trigger-http/src/server.rs | 76 +++++++++++++++++++------------ crates/trigger-http/src/spin.rs | 17 ++++--- crates/trigger-http/src/wagi.rs | 21 ++++----- crates/trigger-http/src/wasi.rs | 12 +++-- crates/trigger-http/src/wasip3.rs | 6 ++- 5 files changed, 77 insertions(+), 55 deletions(-) diff --git a/crates/trigger-http/src/server.rs b/crates/trigger-http/src/server.rs index 8783890ccf..24d106a7ca 100644 --- a/crates/trigger-http/src/server.rs +++ b/crates/trigger-http/src/server.rs @@ -45,6 +45,7 @@ use wasmtime_wasi_http::p2::body::HyperOutgoingBody; use crate::{ Body, InstanceReuseConfig, NotFoundRouteKind, OutputFormat, TlsConfig, TriggerApp, + TriggerInstanceBuilder, headers::strip_forbidden_headers, instrument::{MatchedRoute, finalize_http_span, http_span, instrument_error}, outbound_http::OutboundHttpInterceptor, @@ -178,9 +179,10 @@ impl HttpServer { 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)) => { @@ -327,7 +329,7 @@ impl HttpServer { server_scheme: Scheme, client_addr: SocketAddr, ) -> anyhow::Result> { - set_req_uri(&mut req, server_scheme.clone())?; + set_req_uri(&mut req, server_scheme)?; let app_id = self .trigger_app .app() @@ -353,7 +355,6 @@ impl HttpServer { self.respond_wasm_component( req, route_match, - server_scheme, client_addr, component, &trigger_config.executor, @@ -381,28 +382,10 @@ impl HttpServer { self: &Arc, req: Request, route_match: RouteMatch<'_, '_>, - server_scheme: Scheme, client_addr: SocketAddr, component_id: &str, executor: &Option, ) -> anyhow::Result> { - 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::() - .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 @@ -414,19 +397,19 @@ impl HttpServer { HttpExecutorType::Http => match handler_type { HandlerType::Spin => { SpinHttpExecutor - .execute(instance_builder, &route_match, req, client_addr) + .execute(self, &route_match, req, client_addr, component_id) .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::Wagi(_) => unreachable!(), @@ -441,7 +424,7 @@ impl HttpServer { indices, }; executor - .execute(instance_builder, &route_match, req, client_addr) + .execute(self, &route_match, req, client_addr, component_id) .await } }; @@ -458,6 +441,31 @@ impl HttpServer { } } + pub(crate) fn trigger_instance_builder( + self: &'_ Arc, + component_id: &str, + self_scheme: Option<&Scheme>, + ) -> anyhow::Result> { + 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::() + .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> { @@ -687,9 +695,19 @@ fn set_req_uri(req: &mut Request, scheme: Scheme) -> anyhow::Result<()> { } pub(crate) struct HttpHandlerState { - trigger_app: Arc>, component_id: String, reuse_config: InstanceReuseConfig, + server: OnceLock>>, + self_scheme: OnceLock, +} + +impl HttpHandlerState { + pub(crate) fn init_once(&self, server: &Arc>, 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 HandlerState for HttpHandlerState { @@ -698,8 +716,10 @@ impl HandlerState for HttpHandlerState { fn new_store(&self, _req_id: Option) -> wasmtime::Result> { 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()? diff --git a/crates/trigger-http/src/spin.rs b/crates/trigger-http/src/spin.rs index c2c01b4c9a..87d694f8ef 100644 --- a/crates/trigger-http/src/spin.rs +++ b/crates/trigger-http/src/spin.rs @@ -1,4 +1,4 @@ -use std::net::SocketAddr; +use std::{net::SocketAddr, sync::Arc}; use anyhow::Result; use http_body_util::BodyExt; @@ -10,7 +10,7 @@ use spin_world::v1::http_types; use tracing::{Level, instrument}; use crate::{ - Body, TriggerInstanceBuilder, + Body, HttpServer, headers::{append_headers, prepare_request_headers}, }; @@ -22,19 +22,18 @@ impl SpinHttpExecutor { #[instrument(name = "spin_trigger_http.execute_wasm", skip_all, err(level = Level::INFO), fields(otel.name = format!("execute_wasm_component {}", route_match.lookup_key().to_string())))] pub async fn execute( &self, - instance_builder: TriggerInstanceBuilder<'_, F>, + server: &Arc>, route_match: &RouteMatch<'_, '_>, req: Request, client_addr: SocketAddr, + component_id: &str, ) -> Result> { - let spin_http::routes::TriggerLookupKey::Component(component_id) = route_match.lookup_key() - else { - unreachable!() - }; - tracing::trace!("Executing request using the Spin executor for component {component_id}"); - let (instance, mut store) = instance_builder.instantiate(()).await?; + let (instance, mut store) = server + .trigger_instance_builder(component_id, req.uri().scheme())? + .instantiate(()) + .await?; let headers = prepare_request_headers(&req, route_match, client_addr)?; // Expects here are safe since we have already checked that this diff --git a/crates/trigger-http/src/wagi.rs b/crates/trigger-http/src/wagi.rs index b118ff83a0..8900017a98 100644 --- a/crates/trigger-http/src/wagi.rs +++ b/crates/trigger-http/src/wagi.rs @@ -1,4 +1,4 @@ -use std::{io::Cursor, net::SocketAddr}; +use std::{io::Cursor, net::SocketAddr, sync::Arc}; use anyhow::{Context, Result, ensure}; use http_body_util::BodyExt; @@ -11,7 +11,7 @@ use wasmtime_wasi::p2::bindings::CommandIndices; use wasmtime_wasi::p2::pipe::MemoryOutputPipe; use wasmtime_wasi_http::p2::body::HyperIncomingBody as Body; -use crate::{TriggerInstanceBuilder, headers::compute_default_headers}; +use crate::{HttpServer, headers::compute_default_headers}; pub struct WagiHttpExecutor<'a> { pub wagi_config: &'a WagiTriggerConfig, @@ -22,20 +22,13 @@ impl WagiHttpExecutor<'_> { #[instrument(name = "spin_trigger_http.execute_wagi", skip_all, err(level = Level::INFO), fields(otel.name = format!("execute_wagi_component {}", route_match.lookup_key().to_string())))] pub async fn execute( &self, - mut instance_builder: TriggerInstanceBuilder<'_, F>, + server: &Arc>, route_match: &RouteMatch<'_, '_>, req: Request, client_addr: SocketAddr, + component_id: &str, ) -> Result> { - let spin_http::routes::TriggerLookupKey::Component(component) = route_match.lookup_key() - else { - unreachable!() - }; - - tracing::trace!( - "Executing request using the Wagi executor for component {}", - component - ); + tracing::trace!("Executing request using the Wagi executor for component {component_id}"); let uri_path = req.uri().path(); @@ -79,6 +72,8 @@ impl WagiHttpExecutor<'_> { let stdout = MemoryOutputPipe::new(usize::MAX); + let mut instance_builder = + server.trigger_instance_builder(component_id, parts.uri.scheme())?; let wasi_builder = instance_builder .factor_builder::() .context("The wagi HTTP trigger was configured without the required wasi support")?; @@ -110,7 +105,7 @@ impl WagiHttpExecutor<'_> { let stdout = stdout.try_into_inner().unwrap(); ensure!( !stdout.is_empty(), - "The {component:?} component is configured to use the WAGI executor \ + "The {component_id:?} component is configured to use the WAGI executor \ but did not write to stdout. Check the `executor` in spin.toml." ); diff --git a/crates/trigger-http/src/wasi.rs b/crates/trigger-http/src/wasi.rs index 4cf74191be..c035e6d8c7 100644 --- a/crates/trigger-http/src/wasi.rs +++ b/crates/trigger-http/src/wasi.rs @@ -1,5 +1,6 @@ use std::io::IsTerminal; use std::net::SocketAddr; +use std::sync::Arc; use anyhow::{Context, Result, anyhow}; use futures::TryFutureExt; @@ -16,7 +17,8 @@ use wasmtime_wasi_http::handler::HandlerState; use wasmtime_wasi_http::p2::bindings::http::types::Scheme; use wasmtime_wasi_http::p2::{bindings::Proxy, body::HyperIncomingBody as Body}; -use crate::{TriggerInstanceBuilder, headers::prepare_request_headers}; +use crate::HttpServer; +use crate::headers::prepare_request_headers; pub(super) fn prepare_request( route_match: &RouteMatch<'_, '_>, @@ -55,14 +57,18 @@ impl WasiHttpExecutor<'_, S> { #[instrument(name = "spin_trigger_http.execute_wasm", skip_all, err(level = Level::INFO), fields(otel.name = format!("execute_wasm_component {}", route_match.lookup_key().to_string())))] pub async fn execute( &self, - instance_builder: TriggerInstanceBuilder<'_, F>, + server: &Arc>, route_match: &RouteMatch<'_, '_>, mut req: Request, client_addr: SocketAddr, + component_id: &str, ) -> Result> { prepare_request(route_match, &mut req, client_addr)?; - let (instance, mut store) = instance_builder.instantiate(()).await?; + let (instance, mut store) = server + .trigger_instance_builder(component_id, req.uri().scheme())? + .instantiate(()) + .await?; let mut wasi_http = spin_factor_outbound_http::OutboundHttpFactor::get_wasi_http_impl( store.data_mut().factors_instance_state_mut(), diff --git a/crates/trigger-http/src/wasip3.rs b/crates/trigger-http/src/wasip3.rs index a65e075902..e5793af684 100644 --- a/crates/trigger-http/src/wasip3.rs +++ b/crates/trigger-http/src/wasip3.rs @@ -1,4 +1,4 @@ -use crate::server::HttpHandlerState; +use crate::{HttpServer, server::HttpHandlerState}; use anyhow::{Context as _, Result}; use futures::{FutureExt, channel::oneshot}; use http_body_util::BodyExt; @@ -6,7 +6,7 @@ use spin_factor_outbound_http::{NotifyOnDropBody, p3_to_p2_error_code}; use spin_factors::RuntimeFactors; use spin_factors_executor::InstanceState; use spin_http::routes::RouteMatch; -use std::net::SocketAddr; +use std::{net::SocketAddr, sync::Arc}; use tracing::{Instrument, Level, instrument}; use wasmtime::component::Accessor; use wasmtime_wasi_http::{ @@ -24,10 +24,12 @@ impl Wasip3HttpExecutor<'_, F> { #[instrument(name = "spin_trigger_http.execute_wasm", skip_all, err(level = Level::INFO), fields(otel.name = format!("execute_wasm_component {}", route_match.lookup_key().to_string())))] pub async fn execute( &self, + server: &Arc>, route_match: &RouteMatch<'_, '_>, mut req: http::Request, client_addr: SocketAddr, ) -> Result> { + self.0.state().init_once(server, req.uri()); super::wasi::prepare_request(route_match, &mut req, client_addr)?; let getter = (|data| wasi_http::(data).unwrap())