1use anyhow::Result;
2use spin_factor_wasi::convert;
3use wasmtime::component::{Linker, Resource};
4use wasmtime_wasi_http::p2::WasiHttpCtxView;
5use wasmtime_wasi_http::p2::bindings as latest;
6
7mod bindings {
8 use super::latest;
9
10 wasmtime::component::bindgen!({
11 path: "../../wit",
12 world: "wasi:http/proxy@0.2.0-rc-2023-10-18",
13 imports: { default: trappable },
14 exports: { default: async },
15 with: {
16 "wasi:io/poll.pollable": latest::io::poll::Pollable,
17 "wasi:io/streams.input-stream": latest::io::streams::InputStream,
18 "wasi:io/streams.output-stream": latest::io::streams::OutputStream,
19 "wasi:io/streams.error": latest::io::streams::Error,
20 "wasi:http/types.incoming-response": latest::http::types::IncomingResponse,
21 "wasi:http/types.incoming-request": latest::http::types::IncomingRequest,
22 "wasi:http/types.incoming-body": latest::http::types::IncomingBody,
23 "wasi:http/types.outgoing-response": latest::http::types::OutgoingResponse,
24 "wasi:http/types.outgoing-request": latest::http::types::OutgoingRequest,
25 "wasi:http/types.outgoing-body": latest::http::types::OutgoingBody,
26 "wasi:http/types.fields": latest::http::types::Fields,
27 "wasi:http/types.response-outparam": latest::http::types::ResponseOutparam,
28 "wasi:http/types.future-incoming-response": latest::http::types::FutureIncomingResponse,
29 "wasi:http/types.future-trailers": latest::http::types::FutureTrailers,
30 },
31 });
32}
33
34mod wasi {
35 pub use super::bindings::wasi::{http0_2_0_rc_2023_10_18 as http, io0_2_0_rc_2023_10_18 as io};
36}
37
38pub mod exports {
39 pub mod wasi {
40 pub use super::super::bindings::exports::wasi::http0_2_0_rc_2023_10_18 as http;
41 }
42}
43
44pub use bindings::{Proxy, ProxyIndices};
45use wasi::http::types::{
46 Error as HttpError, Fields, FutureIncomingResponse, FutureTrailers, Headers, IncomingBody,
47 IncomingRequest, IncomingResponse, Method, OutgoingBody, OutgoingRequest, OutgoingResponse,
48 RequestOptions, ResponseOutparam, Scheme, StatusCode, Trailers,
49};
50use wasi::io::poll::Pollable;
51use wasi::io::streams::{InputStream, OutputStream};
52
53use crate::wasi::HasHttp;
54
55pub(crate) fn add_to_linker<T>(
56 linker: &mut Linker<T>,
57 closure: fn(&mut T) -> WasiHttpCtxView<'_>,
58) -> Result<()>
59where
60 T: Send + 'static,
61{
62 wasi::http::types::add_to_linker::<_, HasHttp>(linker, closure)?;
63 wasi::http::outgoing_handler::add_to_linker::<_, HasHttp>(linker, closure)?;
64 Ok(())
65}
66
67impl wasi::http::types::Host for WasiHttpCtxView<'_> {}
68
69impl wasi::http::types::HostFields for WasiHttpCtxView<'_> {
70 fn new(
71 &mut self,
72 entries: Vec<(String, Vec<u8>)>,
73 ) -> wasmtime::Result<wasmtime::component::Resource<Fields>> {
74 latest::http::types::HostFields::from_list(self, entries).map_err(as_wasmtime_error)
75 }
76
77 fn get(
78 &mut self,
79 self_: wasmtime::component::Resource<Fields>,
80 name: String,
81 ) -> wasmtime::Result<Vec<Vec<u8>>> {
82 latest::http::types::HostFields::get(self, self_, name)
83 }
84
85 fn set(
86 &mut self,
87 self_: wasmtime::component::Resource<Fields>,
88 name: String,
89 value: Vec<Vec<u8>>,
90 ) -> wasmtime::Result<()> {
91 latest::http::types::HostFields::set(self, self_, name, value)
92 .map_err(as_wasmtime_error)?;
93 Ok(())
94 }
95
96 fn delete(
97 &mut self,
98 self_: wasmtime::component::Resource<Fields>,
99 name: String,
100 ) -> wasmtime::Result<()> {
101 latest::http::types::HostFields::delete(self, self_, name).map_err(as_wasmtime_error)?;
102 Ok(())
103 }
104
105 fn append(
106 &mut self,
107 self_: wasmtime::component::Resource<Fields>,
108 name: String,
109 value: Vec<u8>,
110 ) -> wasmtime::Result<()> {
111 latest::http::types::HostFields::append(self, self_, name, value)
112 .map_err(as_wasmtime_error)?;
113 Ok(())
114 }
115
116 fn entries(
117 &mut self,
118 self_: wasmtime::component::Resource<Fields>,
119 ) -> wasmtime::Result<Vec<(String, Vec<u8>)>> {
120 latest::http::types::HostFields::entries(self, self_)
121 }
122
123 fn clone(
124 &mut self,
125 self_: wasmtime::component::Resource<Fields>,
126 ) -> wasmtime::Result<wasmtime::component::Resource<Fields>> {
127 latest::http::types::HostFields::clone(self, self_)
128 }
129
130 fn drop(&mut self, rep: wasmtime::component::Resource<Fields>) -> wasmtime::Result<()> {
131 latest::http::types::HostFields::drop(self, rep)
132 }
133}
134
135impl wasi::http::types::HostIncomingRequest for WasiHttpCtxView<'_> {
136 fn method(
137 &mut self,
138 self_: wasmtime::component::Resource<IncomingRequest>,
139 ) -> wasmtime::Result<Method> {
140 latest::http::types::HostIncomingRequest::method(self, self_).map(|e| e.into())
141 }
142
143 fn path_with_query(
144 &mut self,
145 self_: wasmtime::component::Resource<IncomingRequest>,
146 ) -> wasmtime::Result<Option<String>> {
147 latest::http::types::HostIncomingRequest::path_with_query(self, self_)
148 }
149
150 fn scheme(
151 &mut self,
152 self_: wasmtime::component::Resource<IncomingRequest>,
153 ) -> wasmtime::Result<Option<Scheme>> {
154 latest::http::types::HostIncomingRequest::scheme(self, self_).map(|e| e.map(|e| e.into()))
155 }
156
157 fn authority(
158 &mut self,
159 self_: wasmtime::component::Resource<IncomingRequest>,
160 ) -> wasmtime::Result<Option<String>> {
161 latest::http::types::HostIncomingRequest::authority(self, self_)
162 }
163
164 fn headers(
165 &mut self,
166 self_: wasmtime::component::Resource<IncomingRequest>,
167 ) -> wasmtime::Result<wasmtime::component::Resource<Headers>> {
168 latest::http::types::HostIncomingRequest::headers(self, self_)
169 }
170
171 fn consume(
172 &mut self,
173 self_: wasmtime::component::Resource<IncomingRequest>,
174 ) -> wasmtime::Result<Result<wasmtime::component::Resource<IncomingBody>, ()>> {
175 latest::http::types::HostIncomingRequest::consume(self, self_)
176 }
177
178 fn drop(
179 &mut self,
180 rep: wasmtime::component::Resource<IncomingRequest>,
181 ) -> wasmtime::Result<()> {
182 latest::http::types::HostIncomingRequest::drop(self, rep)
183 }
184}
185
186impl wasi::http::types::HostIncomingResponse for WasiHttpCtxView<'_> {
187 fn status(
188 &mut self,
189 self_: wasmtime::component::Resource<IncomingResponse>,
190 ) -> wasmtime::Result<StatusCode> {
191 latest::http::types::HostIncomingResponse::status(self, self_)
192 }
193
194 fn headers(
195 &mut self,
196 self_: wasmtime::component::Resource<IncomingResponse>,
197 ) -> wasmtime::Result<wasmtime::component::Resource<Headers>> {
198 latest::http::types::HostIncomingResponse::headers(self, self_)
199 }
200
201 fn consume(
202 &mut self,
203 self_: wasmtime::component::Resource<IncomingResponse>,
204 ) -> wasmtime::Result<Result<wasmtime::component::Resource<IncomingBody>, ()>> {
205 latest::http::types::HostIncomingResponse::consume(self, self_)
206 }
207
208 fn drop(
209 &mut self,
210 rep: wasmtime::component::Resource<IncomingResponse>,
211 ) -> wasmtime::Result<()> {
212 latest::http::types::HostIncomingResponse::drop(self, rep)
213 }
214}
215
216impl wasi::http::types::HostIncomingBody for WasiHttpCtxView<'_> {
217 fn stream(
218 &mut self,
219 self_: wasmtime::component::Resource<IncomingBody>,
220 ) -> wasmtime::Result<Result<wasmtime::component::Resource<InputStream>, ()>> {
221 latest::http::types::HostIncomingBody::stream(self, self_)
222 }
223
224 fn finish(
225 &mut self,
226 this: wasmtime::component::Resource<IncomingBody>,
227 ) -> wasmtime::Result<wasmtime::component::Resource<FutureTrailers>> {
228 latest::http::types::HostIncomingBody::finish(self, this)
229 }
230
231 fn drop(&mut self, rep: wasmtime::component::Resource<IncomingBody>) -> wasmtime::Result<()> {
232 latest::http::types::HostIncomingBody::drop(self, rep)
233 }
234}
235
236impl wasi::http::types::HostOutgoingRequest for WasiHttpCtxView<'_> {
237 fn new(
238 &mut self,
239 method: Method,
240 path_with_query: Option<String>,
241 scheme: Option<Scheme>,
242 authority: Option<String>,
243 headers: wasmtime::component::Resource<Headers>,
244 ) -> wasmtime::Result<wasmtime::component::Resource<OutgoingRequest>> {
245 let headers = latest::http::types::HostFields::clone(self, headers)?;
246 let request = latest::http::types::HostOutgoingRequest::new(self, headers)?;
247 let borrow = || Resource::new_borrow(request.rep());
248
249 if let Err(()) =
250 latest::http::types::HostOutgoingRequest::set_method(self, borrow(), method.into())?
251 {
252 latest::http::types::HostOutgoingRequest::drop(self, request)?;
253 wasmtime::bail!("invalid method supplied");
254 }
255
256 if let Err(()) = latest::http::types::HostOutgoingRequest::set_path_with_query(
257 self,
258 borrow(),
259 path_with_query,
260 )? {
261 latest::http::types::HostOutgoingRequest::drop(self, request)?;
262 wasmtime::bail!("invalid path-with-query supplied");
263 }
264
265 let authority = authority.unwrap_or_else(|| match &scheme {
269 Some(Scheme::Http) | Some(Scheme::Other(_)) => ":80".to_string(),
270 Some(Scheme::Https) | None => ":443".to_string(),
271 });
272 if let Err(()) = latest::http::types::HostOutgoingRequest::set_scheme(
273 self,
274 borrow(),
275 scheme.map(|s| s.into()),
276 )? {
277 latest::http::types::HostOutgoingRequest::drop(self, request)?;
278 wasmtime::bail!("invalid scheme supplied");
279 }
280
281 if let Err(()) = latest::http::types::HostOutgoingRequest::set_authority(
282 self,
283 borrow(),
284 Some(authority),
285 )? {
286 latest::http::types::HostOutgoingRequest::drop(self, request)?;
287 wasmtime::bail!("invalid authority supplied");
288 }
289
290 Ok(request)
291 }
292
293 fn write(
294 &mut self,
295 self_: wasmtime::component::Resource<OutgoingRequest>,
296 ) -> wasmtime::Result<Result<wasmtime::component::Resource<OutgoingBody>, ()>> {
297 latest::http::types::HostOutgoingRequest::body(self, self_)
298 }
299
300 fn drop(
301 &mut self,
302 rep: wasmtime::component::Resource<OutgoingRequest>,
303 ) -> wasmtime::Result<()> {
304 latest::http::types::HostOutgoingRequest::drop(self, rep)
305 }
306}
307
308impl wasi::http::types::HostOutgoingResponse for WasiHttpCtxView<'_> {
309 fn new(
310 &mut self,
311 status_code: StatusCode,
312 headers: wasmtime::component::Resource<Headers>,
313 ) -> wasmtime::Result<wasmtime::component::Resource<OutgoingResponse>> {
314 let headers = latest::http::types::HostFields::clone(self, headers)?;
315 let response = latest::http::types::HostOutgoingResponse::new(self, headers)?;
316 let borrow = || Resource::new_borrow(response.rep());
317
318 if let Err(()) =
319 latest::http::types::HostOutgoingResponse::set_status_code(self, borrow(), status_code)?
320 {
321 latest::http::types::HostOutgoingResponse::drop(self, response)?;
322 wasmtime::bail!("invalid status code supplied");
323 }
324
325 Ok(response)
326 }
327
328 fn write(
329 &mut self,
330 self_: wasmtime::component::Resource<OutgoingResponse>,
331 ) -> wasmtime::Result<Result<wasmtime::component::Resource<OutgoingBody>, ()>> {
332 latest::http::types::HostOutgoingResponse::body(self, self_)
333 }
334
335 fn drop(
336 &mut self,
337 rep: wasmtime::component::Resource<OutgoingResponse>,
338 ) -> wasmtime::Result<()> {
339 latest::http::types::HostOutgoingResponse::drop(self, rep)
340 }
341}
342
343impl wasi::http::types::HostOutgoingBody for WasiHttpCtxView<'_> {
344 fn write(
345 &mut self,
346 self_: wasmtime::component::Resource<OutgoingBody>,
347 ) -> wasmtime::Result<Result<wasmtime::component::Resource<OutputStream>, ()>> {
348 latest::http::types::HostOutgoingBody::write(self, self_)
349 }
350
351 fn finish(
352 &mut self,
353 this: wasmtime::component::Resource<OutgoingBody>,
354 trailers: Option<wasmtime::component::Resource<Trailers>>,
355 ) -> wasmtime::Result<()> {
356 latest::http::types::HostOutgoingBody::finish(self, this, trailers)?;
357 Ok(())
358 }
359
360 fn drop(&mut self, rep: wasmtime::component::Resource<OutgoingBody>) -> wasmtime::Result<()> {
361 latest::http::types::HostOutgoingBody::drop(self, rep)
362 }
363}
364
365impl wasi::http::types::HostResponseOutparam for WasiHttpCtxView<'_> {
366 fn set(
367 &mut self,
368 param: wasmtime::component::Resource<ResponseOutparam>,
369 response: Result<wasmtime::component::Resource<OutgoingResponse>, HttpError>,
370 ) -> wasmtime::Result<()> {
371 let response = response.map_err(|err| {
372 let msg = match err {
376 HttpError::InvalidUrl(s) => format!("invalid url: {s}"),
377 HttpError::TimeoutError(s) => format!("timeout: {s}"),
378 HttpError::ProtocolError(s) => format!("protocol error: {s}"),
379 HttpError::UnexpectedError(s) => format!("unexpected error: {s}"),
380 };
381 latest::http::types::ErrorCode::InternalError(Some(msg))
382 });
383 latest::http::types::HostResponseOutparam::set(self, param, response)
384 }
385
386 fn drop(
387 &mut self,
388 rep: wasmtime::component::Resource<ResponseOutparam>,
389 ) -> wasmtime::Result<()> {
390 latest::http::types::HostResponseOutparam::drop(self, rep)
391 }
392}
393
394impl wasi::http::types::HostFutureTrailers for WasiHttpCtxView<'_> {
395 fn subscribe(
396 &mut self,
397 self_: wasmtime::component::Resource<FutureTrailers>,
398 ) -> wasmtime::Result<wasmtime::component::Resource<Pollable>> {
399 latest::http::types::HostFutureTrailers::subscribe(self, self_)
400 }
401
402 fn get(
403 &mut self,
404 self_: wasmtime::component::Resource<FutureTrailers>,
405 ) -> wasmtime::Result<Option<Result<wasmtime::component::Resource<Trailers>, HttpError>>> {
406 match latest::http::types::HostFutureTrailers::get(self, self_)? {
407 Some(Ok(Ok(Some(trailers)))) => Ok(Some(Ok(trailers))),
408 Some(Ok(Ok(None))) => Ok(Some(Ok(latest::http::types::HostFields::new(self)?))),
411 Some(Ok(Err(e))) => Ok(Some(Err(e.into()))),
412 Some(Err(())) => wasmtime::bail!("trailers have already been retrieved"),
413 None => Ok(None),
414 }
415 }
416
417 fn drop(&mut self, rep: wasmtime::component::Resource<FutureTrailers>) -> wasmtime::Result<()> {
418 latest::http::types::HostFutureTrailers::drop(self, rep)
419 }
420}
421
422impl wasi::http::types::HostFutureIncomingResponse for WasiHttpCtxView<'_> {
423 fn get(
424 &mut self,
425 self_: wasmtime::component::Resource<FutureIncomingResponse>,
426 ) -> wasmtime::Result<
427 Option<Result<Result<wasmtime::component::Resource<IncomingResponse>, HttpError>, ()>>,
428 > {
429 match latest::http::types::HostFutureIncomingResponse::get(self, self_)? {
430 None => Ok(None),
431 Some(Ok(Ok(response))) => Ok(Some(Ok(Ok(response)))),
432 Some(Ok(Err(e))) => Ok(Some(Ok(Err(e.into())))),
433 Some(Err(())) => Ok(Some(Err(()))),
434 }
435 }
436
437 fn subscribe(
438 &mut self,
439 self_: wasmtime::component::Resource<FutureIncomingResponse>,
440 ) -> wasmtime::Result<wasmtime::component::Resource<Pollable>> {
441 latest::http::types::HostFutureIncomingResponse::subscribe(self, self_)
442 }
443
444 fn drop(
445 &mut self,
446 rep: wasmtime::component::Resource<FutureIncomingResponse>,
447 ) -> wasmtime::Result<()> {
448 latest::http::types::HostFutureIncomingResponse::drop(self, rep)
449 }
450}
451
452impl wasi::http::outgoing_handler::Host for WasiHttpCtxView<'_> {
453 fn handle(
454 &mut self,
455 request: wasmtime::component::Resource<wasi::http::outgoing_handler::OutgoingRequest>,
456 options: Option<RequestOptions>,
457 ) -> wasmtime::Result<
458 Result<
459 wasmtime::component::Resource<FutureIncomingResponse>,
460 wasi::http::outgoing_handler::Error,
461 >,
462 > {
463 let options = match options {
464 Some(RequestOptions {
465 connect_timeout_ms,
466 first_byte_timeout_ms,
467 between_bytes_timeout_ms,
468 }) => {
469 let options = latest::http::types::HostRequestOptions::new(self)?;
470 let borrow = || Resource::new_borrow(request.rep());
471
472 if let Some(ms) = connect_timeout_ms
473 && let Err(()) = latest::http::types::HostRequestOptions::set_connect_timeout(
474 self,
475 borrow(),
476 Some(ms.into()),
477 )?
478 {
479 latest::http::types::HostRequestOptions::drop(self, options)?;
480 wasmtime::bail!("invalid connect timeout supplied");
481 }
482
483 if let Some(ms) = first_byte_timeout_ms
484 && let Err(()) =
485 latest::http::types::HostRequestOptions::set_first_byte_timeout(
486 self,
487 borrow(),
488 Some(ms.into()),
489 )?
490 {
491 latest::http::types::HostRequestOptions::drop(self, options)?;
492 wasmtime::bail!("invalid first byte timeout supplied");
493 }
494
495 if let Some(ms) = between_bytes_timeout_ms
496 && let Err(()) =
497 latest::http::types::HostRequestOptions::set_between_bytes_timeout(
498 self,
499 borrow(),
500 Some(ms.into()),
501 )?
502 {
503 latest::http::types::HostRequestOptions::drop(self, options)?;
504 wasmtime::bail!("invalid between bytes timeout supplied");
505 }
506
507 Some(options)
508 }
509 None => None,
510 };
511 match latest::http::outgoing_handler::Host::handle(self, request, options) {
512 Ok(resp) => Ok(Ok(resp)),
513 Err(e) => Ok(Err(e.downcast()?.into())),
514 }
515 }
516}
517
518convert! {
519 enum latest::http::types::Method [<=>] Method {
520 Get,
521 Head,
522 Post,
523 Put,
524 Delete,
525 Connect,
526 Options,
527 Trace,
528 Patch,
529 Other(e),
530 }
531
532 enum latest::http::types::Scheme [<=>] Scheme {
533 Http,
534 Https,
535 Other(e),
536 }
537}
538
539impl From<latest::http::types::ErrorCode> for HttpError {
540 fn from(e: latest::http::types::ErrorCode) -> HttpError {
541 HttpError::UnexpectedError(e.to_string())
544 }
545}
546
547fn as_wasmtime_error(e: wasmtime_wasi_http::p2::HeaderError) -> wasmtime::Error {
548 match e.downcast() {
549 Ok(e) => wasmtime::Error::new(e),
550 Err(e) => e,
551 }
552}