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 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}