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