Skip to content

Commit 6de4beb

Browse files
committed
make outbound HTTP connection pooling runtime-configurable
It's enabled by default, but can be disabled via a runtime config file containing: ```toml [outbound_http] connection_pooling = false ``` Signed-off-by: Joel Dice <[email protected]>
1 parent b0e484c commit 6de4beb

File tree

8 files changed

+95
-12
lines changed

8 files changed

+95
-12
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/factor-outbound-http/Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ hyper = { workspace = true }
1313
hyper-util = { workspace = true }
1414
reqwest = { workspace = true, features = ["gzip"] }
1515
rustls = { workspace = true }
16+
serde = { workspace = true }
1617
spin-factor-outbound-networking = { path = "../factor-outbound-networking" }
1718
spin-factors = { path = "../factors" }
1819
spin-telemetry = { path = "../telemetry" }
@@ -29,5 +30,10 @@ wasmtime-wasi-http = { workspace = true }
2930
spin-factor-variables = { path = "../factor-variables" }
3031
spin-factors-test = { path = "../factors-test" }
3132

33+
[features]
34+
default = ["spin-cli"]
35+
# Includes the runtime configuration handling used by the Spin CLI
36+
spin-cli = []
37+
3238
[lints]
3339
workspace = true

crates/factor-outbound-http/src/lib.rs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod intercept;
2+
pub mod runtime_config;
23
mod spin;
34
mod wasi;
45
pub mod wasi_2023_10_18;
@@ -12,6 +13,7 @@ use http::{
1213
HeaderValue, Uri,
1314
};
1415
use intercept::OutboundHttpInterceptor;
16+
use runtime_config::RuntimeConfig;
1517
use spin_factor_outbound_networking::{
1618
config::{allowed_hosts::OutboundAllowedHosts, blocked_networks::BlockedNetworks},
1719
ComponentTlsClientConfigs, OutboundNetworkingFactor,
@@ -34,8 +36,8 @@ pub struct OutboundHttpFactor {
3436
}
3537

3638
impl Factor for OutboundHttpFactor {
37-
type RuntimeConfig = ();
38-
type AppState = ();
39+
type RuntimeConfig = RuntimeConfig;
40+
type AppState = AppState;
3941
type InstanceBuilder = InstanceState;
4042

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

4749
fn configure_app<T: RuntimeFactors>(
4850
&self,
49-
_ctx: ConfigureAppContext<T, Self>,
51+
mut ctx: ConfigureAppContext<T, Self>,
5052
) -> anyhow::Result<Self::AppState> {
51-
Ok(())
53+
Ok(AppState {
54+
connection_pooling: ctx
55+
.take_runtime_config()
56+
.unwrap_or_default()
57+
.connection_pooling,
58+
})
5259
}
5360

5461
fn prepare<T: RuntimeFactors>(
@@ -68,6 +75,7 @@ impl Factor for OutboundHttpFactor {
6875
request_interceptor: None,
6976
spin_http_client: None,
7077
wasi_http_clients: None,
78+
connection_pooling: ctx.app_state().connection_pooling,
7179
})
7280
}
7381
}
@@ -83,6 +91,7 @@ pub struct InstanceState {
8391
spin_http_client: Option<reqwest::Client>,
8492
// Connection pooling client for `wasi:http/outgoing-handler` interface
8593
wasi_http_clients: Option<wasi::HttpClients>,
94+
connection_pooling: bool,
8695
}
8796

8897
impl InstanceState {
@@ -160,3 +169,7 @@ impl std::fmt::Display for SelfRequestOrigin {
160169
write!(f, "{}://{}", self.scheme, self.authority)
161170
}
162171
}
172+
173+
pub struct AppState {
174+
connection_pooling: bool,
175+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#[cfg(feature = "spin-cli")]
2+
pub mod spin;
3+
4+
/// Runtime configuration for outbound HTTP.
5+
#[derive(Debug)]
6+
pub struct RuntimeConfig {
7+
/// If true, enable connection pooling and reuse.
8+
pub connection_pooling: bool,
9+
}
10+
11+
impl Default for RuntimeConfig {
12+
fn default() -> Self {
13+
Self {
14+
connection_pooling: true,
15+
}
16+
}
17+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
use serde::Deserialize;
2+
use spin_factors::runtime_config::toml::GetTomlValue;
3+
4+
/// Get the runtime configuration for outbound HTTP from a TOML table.
5+
///
6+
/// Expects table to be in the format:
7+
/// ```toml
8+
/// [outbound_http]
9+
/// connection_pooling = true
10+
/// ```
11+
pub fn config_from_table(
12+
table: &impl GetTomlValue,
13+
) -> anyhow::Result<Option<super::RuntimeConfig>> {
14+
if let Some(outbound_http) = table.get("outbound_http") {
15+
Ok(Some(super::RuntimeConfig {
16+
connection_pooling: outbound_http
17+
.clone()
18+
.try_into::<OutboundHttpToml>()?
19+
.connection_pooling,
20+
}))
21+
} else {
22+
Ok(None)
23+
}
24+
}
25+
26+
#[derive(Debug, Default, Deserialize)]
27+
#[serde(deny_unknown_fields)]
28+
struct OutboundHttpToml {
29+
#[serde(default)]
30+
connection_pooling: bool,
31+
}

crates/factor-outbound-http/src/spin.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,13 @@ impl spin_http::Host for crate::InstanceState {
9090

9191
// Allow reuse of Client's internal connection pool for multiple requests
9292
// in a single component execution
93-
let client = self.spin_http_client.get_or_insert_with(Default::default);
93+
let client = self.spin_http_client.get_or_insert_with(|| {
94+
let mut builder = reqwest::Client::builder();
95+
if !self.connection_pooling {
96+
builder = builder.pool_max_idle_per_host(0);
97+
}
98+
builder.build().unwrap()
99+
});
94100

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

crates/factor-outbound-http/src/wasi.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,15 +135,22 @@ impl WasiHttpView for WasiHttpImplInner<'_> {
135135
request: Request<wasmtime_wasi_http::body::HyperOutgoingBody>,
136136
config: wasmtime_wasi_http::types::OutgoingRequestConfig,
137137
) -> wasmtime_wasi_http::HttpResult<wasmtime_wasi_http::types::HostFutureIncomingResponse> {
138+
let connection_pooling = self.state.connection_pooling;
139+
let builder = move || {
140+
let mut builder = Client::builder(TokioExecutor::new());
141+
if !connection_pooling {
142+
builder.pool_max_idle_per_host(0);
143+
}
144+
builder
145+
};
146+
138147
let http_clients = self
139148
.state
140149
.wasi_http_clients
141150
.get_or_insert_with(|| HttpClients {
142-
http1: Client::builder(TokioExecutor::new()).build(HttpConnector),
143-
http2: Client::builder(TokioExecutor::new())
144-
.http2_only(true)
145-
.build(HttpConnector),
146-
https: Client::builder(TokioExecutor::new()).build(HttpsConnector),
151+
http1: builder().build(HttpConnector),
152+
http2: builder().http2_only(true).build(HttpConnector),
153+
https: builder().build(HttpsConnector),
147154
})
148155
.clone();
149156

crates/runtime-config/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -379,8 +379,10 @@ impl FactorRuntimeConfigSource<WasiFactor> for TomlRuntimeConfigSource<'_, '_> {
379379
}
380380

381381
impl FactorRuntimeConfigSource<OutboundHttpFactor> for TomlRuntimeConfigSource<'_, '_> {
382-
fn get_runtime_config(&mut self) -> anyhow::Result<Option<()>> {
383-
Ok(None)
382+
fn get_runtime_config(
383+
&mut self,
384+
) -> anyhow::Result<Option<<OutboundHttpFactor as spin_factors::Factor>::RuntimeConfig>> {
385+
spin_factor_outbound_http::runtime_config::spin::config_from_table(&self.toml.table)
384386
}
385387
}
386388

0 commit comments

Comments
 (0)