Skip to main content

wasmtime_wasi_http/p3/host/
types.rs

1use crate::FieldMap;
2use crate::p3::bindings::clocks::monotonic_clock::Duration;
3use crate::p3::bindings::http::types::{
4    ErrorCode, FieldName, FieldValue, Fields, HeaderError, Headers, Host, HostFields, HostRequest,
5    HostRequestOptions, HostRequestWithStore, HostResponse, HostResponseWithStore, Method, Request,
6    RequestOptions, RequestOptionsError, Response, Scheme, StatusCode, Trailers,
7};
8use crate::p3::body::Body;
9use crate::p3::{HeaderResult, HttpError, RequestOptionsResult};
10use crate::{WasiHttp, WasiHttpCtxView};
11use std::sync::Arc;
12use wasmtime::component::{Access, FutureReader, Resource, ResourceTable, StreamReader};
13use wasmtime::error::Context as _;
14
15fn get_fields<'a>(
16    table: &'a ResourceTable,
17    fields: &Resource<Fields>,
18) -> wasmtime::Result<&'a Fields> {
19    table
20        .get(&fields)
21        .context("failed to get fields from table")
22}
23
24fn get_fields_mut<'a>(
25    table: &'a mut ResourceTable,
26    fields: &Resource<Fields>,
27) -> HeaderResult<&'a mut Fields> {
28    table
29        .get_mut(&fields)
30        .context("failed to get fields from table")
31        .map_err(crate::p3::HeaderError::trap)
32}
33
34fn push_fields(table: &mut ResourceTable, fields: Fields) -> wasmtime::Result<Resource<Fields>> {
35    table.push(fields).context("failed to push fields to table")
36}
37
38fn delete_fields(table: &mut ResourceTable, fields: Resource<Fields>) -> wasmtime::Result<Fields> {
39    let mut fields = table
40        .delete(fields)
41        .context("failed to delete fields from table")?;
42    // When fields are passed by ownership to the host that flags them as
43    // immutable within `wasi:http`, and this semantically means that putting
44    // fields in a request, then getting them back out, will return an immutable
45    // view of the headers rather than mutable for example.
46    fields.set_immutable();
47    Ok(fields)
48}
49
50fn get_request<'a>(
51    table: &'a ResourceTable,
52    req: &Resource<Request>,
53) -> wasmtime::Result<&'a Request> {
54    table.get(req).context("failed to get request from table")
55}
56
57fn get_request_mut<'a>(
58    table: &'a mut ResourceTable,
59    req: &Resource<Request>,
60) -> wasmtime::Result<&'a mut Request> {
61    table
62        .get_mut(req)
63        .context("failed to get request from table")
64}
65
66fn get_response<'a>(
67    table: &'a ResourceTable,
68    res: &Resource<Response>,
69) -> wasmtime::Result<&'a Response> {
70    table.get(res).context("failed to get response from table")
71}
72
73fn get_response_mut<'a>(
74    table: &'a mut ResourceTable,
75    res: &Resource<Response>,
76) -> wasmtime::Result<&'a mut Response> {
77    table
78        .get_mut(res)
79        .context("failed to get response from table")
80}
81
82fn get_request_options<'a>(
83    table: &'a ResourceTable,
84    opts: &Resource<RequestOptions>,
85) -> wasmtime::Result<&'a RequestOptions> {
86    table
87        .get(opts)
88        .context("failed to get request options from table")
89}
90
91fn get_request_options_mut<'a>(
92    table: &'a mut ResourceTable,
93    opts: &Resource<RequestOptions>,
94) -> RequestOptionsResult<&'a mut RequestOptions> {
95    table
96        .get_mut(opts)
97        .context("failed to get request options from table")
98        .map_err(crate::p3::RequestOptionsError::trap)
99}
100
101fn push_request_options(
102    table: &mut ResourceTable,
103    opts: RequestOptions,
104) -> wasmtime::Result<Resource<RequestOptions>> {
105    table
106        .push(opts)
107        .context("failed to push request options to table")
108}
109
110fn delete_request_options(
111    table: &mut ResourceTable,
112    opts: Resource<RequestOptions>,
113) -> wasmtime::Result<RequestOptions> {
114    table
115        .delete(opts)
116        .context("failed to delete request options from table")
117}
118
119impl HostFields for WasiHttpCtxView<'_> {
120    fn new(&mut self) -> wasmtime::Result<Resource<Fields>> {
121        push_fields(self.table, FieldMap::new_mutable(self.ctx.field_size_limit))
122    }
123
124    fn from_list(
125        &mut self,
126        entries: Vec<(FieldName, FieldValue)>,
127    ) -> HeaderResult<Resource<Fields>> {
128        let mut fields = FieldMap::new_mutable(self.ctx.field_size_limit);
129        for (name, value) in entries {
130            fields.append(self.hooks, name, value)?;
131        }
132        let fields = push_fields(self.table, fields).map_err(crate::p3::HeaderError::trap)?;
133        Ok(fields)
134    }
135
136    fn get(
137        &mut self,
138        fields: Resource<Fields>,
139        name: FieldName,
140    ) -> wasmtime::Result<Vec<FieldValue>> {
141        let fields = get_fields(self.table, &fields)?;
142        Ok(fields
143            .get_all(name)
144            .into_iter()
145            .map(|val| val.as_bytes().into())
146            .collect())
147    }
148
149    fn has(&mut self, fields: Resource<Fields>, name: FieldName) -> wasmtime::Result<bool> {
150        let fields = get_fields(self.table, &fields)?;
151        Ok(fields.contains_key(name))
152    }
153
154    fn set(
155        &mut self,
156        fields: Resource<Fields>,
157        name: FieldName,
158        values: Vec<FieldValue>,
159    ) -> HeaderResult<()> {
160        get_fields_mut(self.table, &fields)?.set(self.hooks, name, values)?;
161        Ok(())
162    }
163
164    fn delete(&mut self, fields: Resource<Fields>, name: FieldName) -> HeaderResult<()> {
165        get_fields_mut(self.table, &fields)?.remove_all(self.hooks, name)?;
166        Ok(())
167    }
168
169    fn get_and_delete(
170        &mut self,
171        fields: Resource<Fields>,
172        name: FieldName,
173    ) -> HeaderResult<Vec<FieldValue>> {
174        let name = name.parse().or(Err(HeaderError::InvalidSyntax))?;
175        let values = get_fields_mut(self.table, &fields)?
176            .remove_all(self.hooks, name)?
177            .into_iter();
178        Ok(values.map(|value| value.as_bytes().into()).collect())
179    }
180
181    fn append(
182        &mut self,
183        fields: Resource<Fields>,
184        name: FieldName,
185        value: FieldValue,
186    ) -> HeaderResult<()> {
187        get_fields_mut(self.table, &fields)?.append(self.hooks, name, value)?;
188        Ok(())
189    }
190
191    fn copy_all(
192        &mut self,
193        fields: Resource<Fields>,
194    ) -> wasmtime::Result<Vec<(FieldName, FieldValue)>> {
195        let fields = get_fields(self.table, &fields)?;
196        let fields = fields
197            .iter()
198            .map(|(name, value)| (name.as_str().into(), value.as_bytes().into()))
199            .collect();
200        Ok(fields)
201    }
202
203    fn clone(&mut self, fields: Resource<Fields>) -> wasmtime::Result<Resource<Fields>> {
204        let mut fields = get_fields(self.table, &fields)?.clone();
205        fields.set_mutable(self.ctx.field_size_limit);
206        push_fields(self.table, fields)
207    }
208
209    fn drop(&mut self, fields: Resource<Fields>) -> wasmtime::Result<()> {
210        delete_fields(self.table, fields)?;
211        Ok(())
212    }
213}
214
215impl<T> HostRequestWithStore<T> for WasiHttp {
216    fn new(
217        mut store: Access<T, Self>,
218        headers: Resource<Headers>,
219        contents: Option<StreamReader<u8>>,
220        trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
221        options: Option<Resource<RequestOptions>>,
222    ) -> wasmtime::Result<(Resource<Request>, FutureReader<Result<(), ErrorCode>>)> {
223        let (body, body_result) = Body::new_guest(&mut store, contents, trailers)?;
224        let WasiHttpCtxView { table, .. } = store.get();
225        let headers = delete_fields(table, headers)?;
226        let options = options
227            .map(|options| delete_request_options(table, options))
228            .transpose()?;
229        let req = Request {
230            method: http::Method::GET,
231            scheme: None,
232            authority: None,
233            path_with_query: None,
234            headers,
235            options: options.map(Into::into),
236            body,
237        };
238        let req = table.push(req).context("failed to push request to table")?;
239        Ok((req, body_result))
240    }
241
242    fn consume_body(
243        mut store: Access<T, Self>,
244        req: Resource<Request>,
245        fut: FutureReader<Result<(), ErrorCode>>,
246    ) -> wasmtime::Result<(
247        StreamReader<u8>,
248        FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
249    )> {
250        let getter = store.getter();
251        let Request { body, .. } = store
252            .get()
253            .table
254            .delete(req)
255            .context("failed to delete request from table")?;
256        body.consume(store, fut, getter)
257    }
258
259    fn drop(mut store: Access<'_, T, Self>, req: Resource<Request>) -> wasmtime::Result<()> {
260        let Request { body, .. } = store
261            .get()
262            .table
263            .delete(req)
264            .context("failed to delete request from table")?;
265        body.drop(store)?;
266        Ok(())
267    }
268}
269
270impl HostRequest for WasiHttpCtxView<'_> {
271    fn get_method(&mut self, req: Resource<Request>) -> wasmtime::Result<Method> {
272        let Request { method, .. } = get_request(self.table, &req)?;
273        Ok(method.into())
274    }
275
276    fn set_method(
277        &mut self,
278        req: Resource<Request>,
279        method: Method,
280    ) -> wasmtime::Result<Result<(), ()>> {
281        let req = get_request_mut(self.table, &req)?;
282        let Ok(method) = method.try_into() else {
283            return Ok(Err(()));
284        };
285        req.method = method;
286        Ok(Ok(()))
287    }
288
289    fn get_path_with_query(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
290        let Request {
291            path_with_query, ..
292        } = get_request(self.table, &req)?;
293        Ok(path_with_query.as_ref().map(|pq| pq.as_str().into()))
294    }
295
296    fn set_path_with_query(
297        &mut self,
298        req: Resource<Request>,
299        path_with_query: Option<String>,
300    ) -> wasmtime::Result<Result<(), ()>> {
301        let req = get_request_mut(self.table, &req)?;
302        let Some(path_with_query) = path_with_query else {
303            req.path_with_query = None;
304            return Ok(Ok(()));
305        };
306        let Ok(path_with_query) = path_with_query.try_into() else {
307            return Ok(Err(()));
308        };
309        req.path_with_query = Some(path_with_query);
310        Ok(Ok(()))
311    }
312
313    fn get_scheme(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<Scheme>> {
314        let Request { scheme, .. } = get_request(self.table, &req)?;
315        Ok(scheme.as_ref().map(Into::into))
316    }
317
318    fn set_scheme(
319        &mut self,
320        req: Resource<Request>,
321        scheme: Option<Scheme>,
322    ) -> wasmtime::Result<Result<(), ()>> {
323        let req = get_request_mut(self.table, &req)?;
324        let Some(scheme) = scheme else {
325            req.scheme = None;
326            return Ok(Ok(()));
327        };
328        let Ok(scheme) = scheme.try_into() else {
329            return Ok(Err(()));
330        };
331        req.scheme = Some(scheme);
332        Ok(Ok(()))
333    }
334
335    fn get_authority(&mut self, req: Resource<Request>) -> wasmtime::Result<Option<String>> {
336        let Request { authority, .. } = get_request(self.table, &req)?;
337        Ok(authority.as_ref().map(|auth| auth.as_str().into()))
338    }
339
340    fn set_authority(
341        &mut self,
342        req: Resource<Request>,
343        authority: Option<String>,
344    ) -> wasmtime::Result<Result<(), ()>> {
345        let req = get_request_mut(self.table, &req)?;
346        let Some(authority) = authority else {
347            req.authority = None;
348            return Ok(Ok(()));
349        };
350        let Ok(authority) = crate::parse_authority(authority) else {
351            return Ok(Err(()));
352        };
353        req.authority = Some(authority);
354        Ok(Ok(()))
355    }
356
357    fn get_options(
358        &mut self,
359        req: Resource<Request>,
360    ) -> wasmtime::Result<Option<Resource<RequestOptions>>> {
361        let Request { options, .. } = get_request(self.table, &req)?;
362        if let Some(options) = options {
363            let options = push_request_options(
364                self.table,
365                RequestOptions::new_immutable(Arc::clone(options)),
366            )?;
367            Ok(Some(options))
368        } else {
369            Ok(None)
370        }
371    }
372
373    fn get_headers(&mut self, req: Resource<Request>) -> wasmtime::Result<Resource<Headers>> {
374        let Request { headers, .. } = get_request(self.table, &req)?;
375        push_fields(self.table, headers.clone())
376    }
377}
378
379impl HostRequestOptions for WasiHttpCtxView<'_> {
380    fn new(&mut self) -> wasmtime::Result<Resource<RequestOptions>> {
381        push_request_options(self.table, RequestOptions::new_mutable_default())
382    }
383
384    fn get_connect_timeout(
385        &mut self,
386        opts: Resource<RequestOptions>,
387    ) -> wasmtime::Result<Option<Duration>> {
388        let opts = get_request_options(self.table, &opts)?;
389        let Some(connect_timeout) = opts.connect_timeout else {
390            return Ok(None);
391        };
392        let ns = connect_timeout.as_nanos();
393        let ns = Duration::try_from(ns)
394            .context("connect timeout duration nanoseconds do not fit in u64")?;
395        Ok(Some(ns))
396    }
397
398    fn set_connect_timeout(
399        &mut self,
400        opts: Resource<RequestOptions>,
401        duration: Option<Duration>,
402    ) -> RequestOptionsResult<()> {
403        let opts = get_request_options_mut(self.table, &opts)?;
404        let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
405        opts.connect_timeout = duration.map(core::time::Duration::from_nanos);
406        Ok(())
407    }
408
409    fn get_first_byte_timeout(
410        &mut self,
411        opts: Resource<RequestOptions>,
412    ) -> wasmtime::Result<Option<Duration>> {
413        let opts = get_request_options(self.table, &opts)?;
414        let Some(first_byte_timeout) = opts.first_byte_timeout else {
415            return Ok(None);
416        };
417        let ns = first_byte_timeout.as_nanos();
418        let ns = Duration::try_from(ns)
419            .context("first byte timeout duration nanoseconds do not fit in u64")?;
420        Ok(Some(ns))
421    }
422
423    fn set_first_byte_timeout(
424        &mut self,
425        opts: Resource<RequestOptions>,
426        duration: Option<Duration>,
427    ) -> RequestOptionsResult<()> {
428        let opts = get_request_options_mut(self.table, &opts)?;
429        let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
430        opts.first_byte_timeout = duration.map(core::time::Duration::from_nanos);
431        Ok(())
432    }
433
434    fn get_between_bytes_timeout(
435        &mut self,
436        opts: Resource<RequestOptions>,
437    ) -> wasmtime::Result<Option<Duration>> {
438        let opts = get_request_options(self.table, &opts)?;
439        let Some(between_bytes_timeout) = opts.between_bytes_timeout else {
440            return Ok(None);
441        };
442        let ns = between_bytes_timeout.as_nanos();
443        let ns = Duration::try_from(ns)
444            .context("between bytes timeout duration nanoseconds do not fit in u64")?;
445        Ok(Some(ns))
446    }
447
448    fn set_between_bytes_timeout(
449        &mut self,
450        opts: Resource<RequestOptions>,
451        duration: Option<Duration>,
452    ) -> RequestOptionsResult<()> {
453        let opts = get_request_options_mut(self.table, &opts)?;
454        let opts = opts.get_mut().ok_or(RequestOptionsError::Immutable)?;
455        opts.between_bytes_timeout = duration.map(core::time::Duration::from_nanos);
456        Ok(())
457    }
458
459    fn clone(
460        &mut self,
461        opts: Resource<RequestOptions>,
462    ) -> wasmtime::Result<Resource<RequestOptions>> {
463        let opts = get_request_options(self.table, &opts)?;
464        push_request_options(self.table, RequestOptions::new_mutable(Arc::clone(opts)))
465    }
466
467    fn drop(&mut self, opts: Resource<RequestOptions>) -> wasmtime::Result<()> {
468        delete_request_options(self.table, opts)?;
469        Ok(())
470    }
471}
472
473impl<T> HostResponseWithStore<T> for WasiHttp {
474    fn new(
475        mut store: Access<T, Self>,
476        headers: Resource<Headers>,
477        contents: Option<StreamReader<u8>>,
478        trailers: FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
479    ) -> wasmtime::Result<(Resource<Response>, FutureReader<Result<(), ErrorCode>>)> {
480        let (body, body_result) = Body::new_guest(&mut store, contents, trailers)?;
481        let WasiHttpCtxView { table, .. } = store.get();
482        let headers = delete_fields(table, headers)?;
483        let res = Response {
484            status: http::StatusCode::OK,
485            headers,
486            body,
487        };
488        let res = table
489            .push(res)
490            .context("failed to push response to table")?;
491        Ok((res, body_result))
492    }
493
494    fn consume_body(
495        mut store: Access<T, Self>,
496        res: Resource<Response>,
497        fut: FutureReader<Result<(), ErrorCode>>,
498    ) -> wasmtime::Result<(
499        StreamReader<u8>,
500        FutureReader<Result<Option<Resource<Trailers>>, ErrorCode>>,
501    )> {
502        let getter = store.getter();
503        let Response { body, .. } = store
504            .get()
505            .table
506            .delete(res)
507            .context("failed to delete response from table")?;
508        body.consume(store, fut, getter)
509    }
510
511    fn drop(mut store: Access<'_, T, Self>, res: Resource<Response>) -> wasmtime::Result<()> {
512        let Response { body, .. } = store
513            .get()
514            .table
515            .delete(res)
516            .context("failed to delete response from table")?;
517        body.drop(store)?;
518        Ok(())
519    }
520}
521
522impl HostResponse for WasiHttpCtxView<'_> {
523    fn get_status_code(&mut self, res: Resource<Response>) -> wasmtime::Result<StatusCode> {
524        let res = get_response(self.table, &res)?;
525        Ok(res.status.into())
526    }
527
528    fn set_status_code(
529        &mut self,
530        res: Resource<Response>,
531        status_code: StatusCode,
532    ) -> wasmtime::Result<Result<(), ()>> {
533        let res = get_response_mut(self.table, &res)?;
534        match http::StatusCode::from_u16(status_code) {
535            Ok(status) if matches!(status_code, 100..=599) => {
536                res.status = status;
537                Ok(Ok(()))
538            }
539            _ => Ok(Err(())),
540        }
541    }
542
543    fn get_headers(&mut self, res: Resource<Response>) -> wasmtime::Result<Resource<Headers>> {
544        let Response { headers, .. } = get_response(self.table, &res)?;
545        push_fields(self.table, headers.clone())
546    }
547}
548
549impl Host for WasiHttpCtxView<'_> {
550    fn convert_error_code(&mut self, error: HttpError) -> wasmtime::Result<ErrorCode> {
551        error.downcast()
552    }
553
554    fn convert_header_error(
555        &mut self,
556        error: crate::p3::HeaderError,
557    ) -> wasmtime::Result<HeaderError> {
558        error.downcast()
559    }
560
561    fn convert_request_options_error(
562        &mut self,
563        error: crate::p3::RequestOptionsError,
564    ) -> wasmtime::Result<RequestOptionsError> {
565        error.downcast()
566    }
567}