Skip to content

enable connection pooling and reuse for outbound wasi-http #3229

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 15, 2025
Merged
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
5 changes: 5 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@ jobs:
steps:
- uses: actions/checkout@v3

- name: setup dependencies
uses: ./.github/actions/spin-ci-dependencies
with:
rust: true

# Install all the toolchain dependencies
- name: Install Rust wasm target
run: rustup target add wasm32-wasip1 wasm32-unknown-unknown
Expand Down
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ tokio = "1"
tokio-rustls = { version = "0.26", default-features = false, features = ["logging", "tls12"] }
toml = "0.8"
toml_edit = "0.22"
tower-service = "0.3.3"
tracing = { version = "0.1.41", features = ["log"] }
url = "2"
walkdir = "2"
Expand All @@ -186,4 +187,4 @@ blocks_in_conditions = "allow"

[[bin]]
name = "spin"
path = "src/bin/spin.rs"
path = "src/bin/spin.rs"
7 changes: 7 additions & 0 deletions crates/factor-outbound-http/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,14 @@ hyper = { workspace = true }
hyper-util = { workspace = true }
reqwest = { workspace = true, features = ["gzip"] }
rustls = { workspace = true }
serde = { workspace = true }
spin-factor-outbound-networking = { path = "../factor-outbound-networking" }
spin-factors = { path = "../factors" }
spin-telemetry = { path = "../telemetry" }
spin-world = { path = "../world" }
tokio = { workspace = true, features = ["macros", "rt", "net"] }
tokio-rustls = { workspace = true }
tower-service = { workspace = true }
tracing = { workspace = true }
wasmtime = { workspace = true }
wasmtime-wasi = { workspace = true }
Expand All @@ -28,5 +30,10 @@ wasmtime-wasi-http = { workspace = true }
spin-factor-variables = { path = "../factor-variables" }
spin-factors-test = { path = "../factors-test" }

[features]
default = ["spin-cli"]
# Includes the runtime configuration handling used by the Spin CLI
spin-cli = []

[lints]
workspace = true
24 changes: 20 additions & 4 deletions crates/factor-outbound-http/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
pub mod intercept;
pub mod runtime_config;
mod spin;
mod wasi;
pub mod wasi_2023_10_18;
Expand All @@ -12,6 +13,7 @@ use http::{
HeaderValue, Uri,
};
use intercept::OutboundHttpInterceptor;
use runtime_config::RuntimeConfig;
use spin_factor_outbound_networking::{
config::{allowed_hosts::OutboundAllowedHosts, blocked_networks::BlockedNetworks},
ComponentTlsClientConfigs, OutboundNetworkingFactor,
Expand All @@ -34,8 +36,8 @@ pub struct OutboundHttpFactor {
}

impl Factor for OutboundHttpFactor {
type RuntimeConfig = ();
type AppState = ();
type RuntimeConfig = RuntimeConfig;
type AppState = AppState;
type InstanceBuilder = InstanceState;

fn init(&mut self, ctx: &mut impl spin_factors::InitContext<Self>) -> anyhow::Result<()> {
Expand All @@ -46,9 +48,14 @@ impl Factor for OutboundHttpFactor {

fn configure_app<T: RuntimeFactors>(
&self,
_ctx: ConfigureAppContext<T, Self>,
mut ctx: ConfigureAppContext<T, Self>,
) -> anyhow::Result<Self::AppState> {
Ok(())
Ok(AppState {
connection_pooling: ctx
.take_runtime_config()
.unwrap_or_default()
.connection_pooling,
})
}

fn prepare<T: RuntimeFactors>(
Expand All @@ -67,6 +74,8 @@ impl Factor for OutboundHttpFactor {
self_request_origin: None,
request_interceptor: None,
spin_http_client: None,
wasi_http_clients: None,
connection_pooling: ctx.app_state().connection_pooling,
})
}
}
Expand All @@ -80,6 +89,9 @@ pub struct InstanceState {
request_interceptor: Option<Arc<dyn OutboundHttpInterceptor>>,
// Connection-pooling client for 'fermyon:spin/http' interface
spin_http_client: Option<reqwest::Client>,
// Connection pooling client for `wasi:http/outgoing-handler` interface
wasi_http_clients: Option<wasi::HttpClients>,
connection_pooling: bool,
}

impl InstanceState {
Expand Down Expand Up @@ -157,3 +169,7 @@ impl std::fmt::Display for SelfRequestOrigin {
write!(f, "{}://{}", self.scheme, self.authority)
}
}

pub struct AppState {
connection_pooling: bool,
}
17 changes: 17 additions & 0 deletions crates/factor-outbound-http/src/runtime_config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#[cfg(feature = "spin-cli")]
pub mod spin;

/// Runtime configuration for outbound HTTP.
#[derive(Debug)]
pub struct RuntimeConfig {
/// If true, enable connection pooling and reuse.
pub connection_pooling: bool,
}

impl Default for RuntimeConfig {
fn default() -> Self {
Self {
connection_pooling: true,
}
}
}
31 changes: 31 additions & 0 deletions crates/factor-outbound-http/src/runtime_config/spin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use serde::Deserialize;
use spin_factors::runtime_config::toml::GetTomlValue;

/// Get the runtime configuration for outbound HTTP from a TOML table.
///
/// Expects table to be in the format:
/// ```toml
/// [outbound_http]
/// connection_pooling = true
/// ```
pub fn config_from_table(
table: &impl GetTomlValue,
) -> anyhow::Result<Option<super::RuntimeConfig>> {
if let Some(outbound_http) = table.get("outbound_http") {
Ok(Some(super::RuntimeConfig {
connection_pooling: outbound_http
.clone()
.try_into::<OutboundHttpToml>()?
.connection_pooling,
}))
} else {
Ok(None)
}
}

#[derive(Debug, Default, Deserialize)]
#[serde(deny_unknown_fields)]
struct OutboundHttpToml {
#[serde(default)]
connection_pooling: bool,
}
8 changes: 7 additions & 1 deletion crates/factor-outbound-http/src/spin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,13 @@ impl spin_http::Host for crate::InstanceState {

// Allow reuse of Client's internal connection pool for multiple requests
// in a single component execution
let client = self.spin_http_client.get_or_insert_with(Default::default);
let client = self.spin_http_client.get_or_insert_with(|| {
let mut builder = reqwest::Client::builder();
if !self.connection_pooling {
builder = builder.pool_max_idle_per_host(0);
}
builder.build().unwrap()
});

let resp = client.execute(req).await.map_err(log_reqwest_error)?;

Expand Down
Loading