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