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 BlockedNetworks, ComponentTlsClientConfigs, 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
29#[derive(Default)]
30pub struct OutboundHttpFactor {
31 _priv: (),
32}
33
34impl Factor for OutboundHttpFactor {
35 type RuntimeConfig = ();
36 type AppState = ();
37 type InstanceBuilder = InstanceState;
38
39 fn init(&mut self, ctx: &mut impl spin_factors::InitContext<Self>) -> anyhow::Result<()> {
40 ctx.link_bindings(spin_world::v1::http::add_to_linker)?;
41 wasi::add_to_linker(ctx)?;
42 Ok(())
43 }
44
45 fn configure_app<T: RuntimeFactors>(
46 &self,
47 _ctx: ConfigureAppContext<T, Self>,
48 ) -> anyhow::Result<Self::AppState> {
49 Ok(())
50 }
51
52 fn prepare<T: RuntimeFactors>(
53 &self,
54 mut ctx: PrepareContext<T, Self>,
55 ) -> anyhow::Result<Self::InstanceBuilder> {
56 let outbound_networking = ctx.instance_builder::<OutboundNetworkingFactor>()?;
57 let allowed_hosts = outbound_networking.allowed_hosts();
58 let blocked_networks = outbound_networking.blocked_networks();
59 let component_tls_configs = outbound_networking.component_tls_configs();
60 Ok(InstanceState {
61 wasi_http_ctx: WasiHttpCtx::new(),
62 allowed_hosts,
63 blocked_networks,
64 component_tls_configs,
65 self_request_origin: None,
66 request_interceptor: None,
67 spin_http_client: None,
68 })
69 }
70}
71
72pub struct InstanceState {
73 wasi_http_ctx: WasiHttpCtx,
74 allowed_hosts: OutboundAllowedHosts,
75 blocked_networks: BlockedNetworks,
76 component_tls_configs: ComponentTlsClientConfigs,
77 self_request_origin: Option<SelfRequestOrigin>,
78 request_interceptor: Option<Arc<dyn OutboundHttpInterceptor>>,
79 spin_http_client: Option<reqwest::Client>,
81}
82
83impl InstanceState {
84 pub fn set_self_request_origin(&mut self, origin: SelfRequestOrigin) {
89 self.self_request_origin = Some(origin);
90 }
91
92 pub fn set_request_interceptor(
96 &mut self,
97 interceptor: impl OutboundHttpInterceptor + 'static,
98 ) -> anyhow::Result<()> {
99 if self.request_interceptor.is_some() {
100 anyhow::bail!("set_request_interceptor can only be called once");
101 }
102 self.request_interceptor = Some(Arc::new(interceptor));
103 Ok(())
104 }
105}
106
107impl SelfInstanceBuilder for InstanceState {}
108
109pub type Request = http::Request<wasmtime_wasi_http::body::HyperOutgoingBody>;
110pub type Response = http::Response<wasmtime_wasi_http::body::HyperIncomingBody>;
111
112#[derive(Clone, Debug)]
114pub struct SelfRequestOrigin {
115 pub scheme: Scheme,
116 pub authority: Authority,
117}
118
119impl SelfRequestOrigin {
120 pub fn create(scheme: Scheme, auth: &str) -> anyhow::Result<Self> {
121 Ok(SelfRequestOrigin {
122 scheme,
123 authority: auth
124 .parse()
125 .with_context(|| format!("address '{auth}' is not a valid authority"))?,
126 })
127 }
128
129 pub fn from_uri(uri: &Uri) -> anyhow::Result<Self> {
130 Ok(Self {
131 scheme: uri.scheme().context("URI missing scheme")?.clone(),
132 authority: uri.authority().context("URI missing authority")?.clone(),
133 })
134 }
135
136 fn into_uri(self, path_and_query: Option<PathAndQuery>) -> Uri {
137 let mut parts = Parts::default();
138 parts.scheme = Some(self.scheme);
139 parts.authority = Some(self.authority);
140 parts.path_and_query = path_and_query;
141 Uri::from_parts(parts).unwrap()
142 }
143
144 fn use_tls(&self) -> bool {
145 self.scheme == Scheme::HTTPS
146 }
147
148 fn host_header(&self) -> HeaderValue {
149 HeaderValue::from_str(self.authority.as_str()).unwrap()
150 }
151}
152
153impl std::fmt::Display for SelfRequestOrigin {
154 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
155 write!(f, "{}://{}", self.scheme, self.authority)
156 }
157}