spin_factor_outbound_http/
lib.rs1pub mod intercept;
2mod spin;
3mod wasi;
4pub mod wasi_2023_10_18;
5pub mod wasi_2023_11_10;
6
7use std::sync::Arc;
8
9use anyhow::Context;
10use http::{
11 uri::{Authority, Parts, PathAndQuery, Scheme},
12 HeaderValue, Uri,
13};
14use intercept::OutboundHttpInterceptor;
15use spin_factor_outbound_networking::{
16 ComponentTlsConfigs, OutboundAllowedHosts, OutboundNetworkingFactor,
17};
18use spin_factors::{
19 anyhow, ConfigureAppContext, Factor, PrepareContext, RuntimeFactors, SelfInstanceBuilder,
20};
21use wasmtime_wasi_http::WasiHttpCtx;
22
23pub use wasmtime_wasi_http::{
24 body::HyperOutgoingBody,
25 types::{HostFutureIncomingResponse, OutgoingRequestConfig},
26 HttpResult,
27};
28
29pub struct OutboundHttpFactor {
30 allow_private_ips: bool,
31}
32
33impl OutboundHttpFactor {
34 pub fn new(allow_private_ips: bool) -> Self {
38 Self { allow_private_ips }
39 }
40}
41
42impl Default for OutboundHttpFactor {
43 fn default() -> Self {
44 Self {
45 allow_private_ips: true,
46 }
47 }
48}
49
50impl Factor for OutboundHttpFactor {
51 type RuntimeConfig = ();
52 type AppState = ();
53 type InstanceBuilder = InstanceState;
54
55 fn init<T: Send + 'static>(
56 &mut self,
57 mut ctx: spin_factors::InitContext<T, Self>,
58 ) -> anyhow::Result<()> {
59 ctx.link_bindings(spin_world::v1::http::add_to_linker)?;
60 wasi::add_to_linker::<T>(&mut ctx)?;
61 Ok(())
62 }
63
64 fn configure_app<T: RuntimeFactors>(
65 &self,
66 _ctx: ConfigureAppContext<T, Self>,
67 ) -> anyhow::Result<Self::AppState> {
68 Ok(())
69 }
70
71 fn prepare<T: RuntimeFactors>(
72 &self,
73 mut ctx: PrepareContext<T, Self>,
74 ) -> anyhow::Result<Self::InstanceBuilder> {
75 let outbound_networking = ctx.instance_builder::<OutboundNetworkingFactor>()?;
76 let allowed_hosts = outbound_networking.allowed_hosts();
77 let component_tls_configs = outbound_networking.component_tls_configs().clone();
78 Ok(InstanceState {
79 wasi_http_ctx: WasiHttpCtx::new(),
80 allowed_hosts,
81 allow_private_ips: self.allow_private_ips,
82 component_tls_configs,
83 self_request_origin: None,
84 request_interceptor: None,
85 spin_http_client: None,
86 })
87 }
88}
89
90pub struct InstanceState {
91 wasi_http_ctx: WasiHttpCtx,
92 allowed_hosts: OutboundAllowedHosts,
93 allow_private_ips: bool,
94 component_tls_configs: ComponentTlsConfigs,
95 self_request_origin: Option<SelfRequestOrigin>,
96 request_interceptor: Option<Arc<dyn OutboundHttpInterceptor>>,
97 spin_http_client: Option<reqwest::Client>,
99}
100
101impl InstanceState {
102 pub fn set_self_request_origin(&mut self, origin: SelfRequestOrigin) {
107 self.self_request_origin = Some(origin);
108 }
109
110 pub fn set_request_interceptor(
114 &mut self,
115 interceptor: impl OutboundHttpInterceptor + 'static,
116 ) -> anyhow::Result<()> {
117 if self.request_interceptor.is_some() {
118 anyhow::bail!("set_request_interceptor can only be called once");
119 }
120 self.request_interceptor = Some(Arc::new(interceptor));
121 Ok(())
122 }
123}
124
125impl SelfInstanceBuilder for InstanceState {}
126
127pub type Request = http::Request<wasmtime_wasi_http::body::HyperOutgoingBody>;
128pub type Response = http::Response<wasmtime_wasi_http::body::HyperIncomingBody>;
129
130#[derive(Clone, Debug)]
132pub struct SelfRequestOrigin {
133 pub scheme: Scheme,
134 pub authority: Authority,
135}
136
137impl SelfRequestOrigin {
138 pub fn create(scheme: Scheme, auth: &str) -> anyhow::Result<Self> {
139 Ok(SelfRequestOrigin {
140 scheme,
141 authority: auth
142 .parse()
143 .with_context(|| format!("address '{auth}' is not a valid authority"))?,
144 })
145 }
146
147 pub fn from_uri(uri: &Uri) -> anyhow::Result<Self> {
148 Ok(Self {
149 scheme: uri.scheme().context("URI missing scheme")?.clone(),
150 authority: uri.authority().context("URI missing authority")?.clone(),
151 })
152 }
153
154 fn into_uri(self, path_and_query: Option<PathAndQuery>) -> Uri {
155 let mut parts = Parts::default();
156 parts.scheme = Some(self.scheme);
157 parts.authority = Some(self.authority);
158 parts.path_and_query = path_and_query;
159 Uri::from_parts(parts).unwrap()
160 }
161
162 fn use_tls(&self) -> bool {
163 self.scheme == Scheme::HTTPS
164 }
165
166 fn host_header(&self) -> HeaderValue {
167 HeaderValue::from_str(self.authority.as_str()).unwrap()
168 }
169}
170
171impl std::fmt::Display for SelfRequestOrigin {
172 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173 write!(f, "{}://{}", self.scheme, self.authority)
174 }
175}