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