Skip to main content

wasmtime_wasi_http/p2/
body.rs

1//! Implementation of the `wasi:http/types` interface's various body types.
2
3use crate::p2::bindings::http::types;
4use crate::{Error, FieldMap};
5use bytes::Bytes;
6use http_body::{Body, Frame};
7use http_body_util::BodyExt;
8use http_body_util::combinators::UnsyncBoxBody;
9use std::future::Future;
10use std::mem;
11use std::task::{Context, Poll};
12use std::{pin::Pin, sync::Arc};
13use tokio::sync::{mpsc, oneshot};
14use wasmtime::format_err;
15use wasmtime_wasi::p2::{InputStream, OutputStream, Pollable, StreamError};
16use wasmtime_wasi::runtime::{AbortOnDropJoinHandle, poll_noop};
17
18/// Common type for incoming bodies.
19pub type HyperIncomingBody = UnsyncBoxBody<Bytes, Error>;
20
21/// Common type for outgoing bodies.
22pub type HyperOutgoingBody = UnsyncBoxBody<Bytes, Error>;
23
24/// The concrete type behind a `was:http/types.incoming-body` resource.
25#[derive(Debug)]
26pub struct HostIncomingBody {
27    body: IncomingBodyState,
28    /// An optional worker task to keep alive while this body is being read.
29    /// This ensures that if the parent of this body is dropped before the body
30    /// then the backing data behind this worker is kept alive.
31    worker: Option<AbortOnDropJoinHandle<()>>,
32}
33
34impl HostIncomingBody {
35    /// Create a new `HostIncomingBody` with the given `body` and a per-frame timeout
36    pub fn new(body: HyperIncomingBody) -> HostIncomingBody {
37        HostIncomingBody {
38            body: IncomingBodyState::Start(body),
39            worker: None,
40        }
41    }
42
43    /// Retain a worker task that needs to be kept alive while this body is being read.
44    pub fn retain_worker(&mut self, worker: AbortOnDropJoinHandle<()>) {
45        assert!(self.worker.is_none());
46        self.worker = Some(worker);
47    }
48
49    /// Try taking the stream of this body, if it's available.
50    pub fn take_stream(&mut self) -> Option<HostIncomingBodyStream> {
51        match &mut self.body {
52            IncomingBodyState::Start(_) => {}
53            IncomingBodyState::InBodyStream(_) => return None,
54        }
55        let (tx, rx) = oneshot::channel();
56        let body = match mem::replace(&mut self.body, IncomingBodyState::InBodyStream(rx)) {
57            IncomingBodyState::Start(b) => b,
58            IncomingBodyState::InBodyStream(_) => unreachable!(),
59        };
60        Some(HostIncomingBodyStream {
61            state: IncomingBodyStreamState::Open { body, tx },
62            buffer: Bytes::new(),
63            error: None,
64        })
65    }
66
67    /// Convert this body into a `HostFutureTrailers` resource.
68    pub fn into_future_trailers(self) -> HostFutureTrailers {
69        HostFutureTrailers::Waiting(self)
70    }
71}
72
73/// Internal state of a [`HostIncomingBody`].
74#[derive(Debug)]
75enum IncomingBodyState {
76    /// The body is stored here meaning that within `HostIncomingBody` the
77    /// `take_stream` method can be called for example.
78    Start(HyperIncomingBody),
79
80    /// The body is within a `HostIncomingBodyStream` meaning that it's not
81    /// currently owned here. The body will be sent back over this channel when
82    /// it's done, however.
83    InBodyStream(oneshot::Receiver<StreamEnd>),
84}
85
86/// Message sent when a `HostIncomingBodyStream` is done to the
87/// `HostFutureTrailers` state.
88#[derive(Debug)]
89enum StreamEnd {
90    /// The body wasn't completely read and was dropped early. May still have
91    /// trailers, but requires reading more frames.
92    Remaining(HyperIncomingBody),
93
94    /// Body was completely read and trailers were read. Here are the trailers.
95    /// Note that `None` means that the body finished without trailers.
96    Trailers(Option<http::HeaderMap>),
97
98    /// The body finished with an error. `future-trailers` reports it as well,
99    /// so a guest that only reads the trailers still sees the failure.
100    Error(Arc<Error>),
101}
102
103/// The concrete type behind the `wasi:io/streams.input-stream` resource returned
104/// by `wasi:http/types.incoming-body`'s `stream` method.
105#[derive(Debug)]
106pub struct HostIncomingBodyStream {
107    state: IncomingBodyStreamState,
108    buffer: Bytes,
109    error: Option<Arc<Error>>,
110}
111
112impl HostIncomingBodyStream {
113    fn record_frame(&mut self, frame: Option<Result<Frame<Bytes>, Error>>) {
114        match frame {
115            Some(Ok(frame)) => match frame.into_data() {
116                // A data frame was received, so queue up the buffered data for
117                // the next `read` call.
118                Ok(bytes) => {
119                    assert!(self.buffer.is_empty());
120                    self.buffer = bytes;
121                }
122
123                // Trailers were received meaning that this was the final frame.
124                // Throw away the body and send the trailers along the
125                // `tx` channel to make them available.
126                Err(trailers) => {
127                    let trailers = trailers.into_trailers().unwrap();
128                    let tx = match mem::replace(&mut self.state, IncomingBodyStreamState::Closed) {
129                        IncomingBodyStreamState::Open { body: _, tx } => tx,
130                        IncomingBodyStreamState::Closed => unreachable!(),
131                    };
132
133                    // NB: ignore send failures here because if this fails then
134                    // no one was interested in the trailers.
135                    let _ = tx.send(StreamEnd::Trailers(Some(trailers)));
136                }
137            },
138
139            // An error was received meaning that the stream is now done.
140            // Destroy the body to terminate the stream while enqueueing the
141            // error to get returned from the next call to `read`.
142            Some(Err(e)) => {
143                // Also tell `future-trailers`. Both paths report the real
144                // error, so it's shared rather than replaced by an error-code.
145                let tx = match mem::replace(&mut self.state, IncomingBodyStreamState::Closed) {
146                    IncomingBodyStreamState::Open { tx, .. } => Some(tx),
147                    IncomingBodyStreamState::Closed => None,
148                };
149                let e = Arc::new(e);
150                if let Some(tx) = tx {
151                    let _ = tx.send(StreamEnd::Error(Arc::clone(&e)));
152                }
153                self.error = Some(e);
154            }
155
156            // No more frames are going to be received again, so drop the `body`
157            // and the `tx` channel we'd send the body back onto because it's
158            // not needed as frames are done.
159            None => {
160                self.state = IncomingBodyStreamState::Closed;
161            }
162        }
163    }
164}
165
166#[derive(Debug)]
167enum IncomingBodyStreamState {
168    /// The body is currently open for reading and present here.
169    ///
170    /// When trailers are read, or when this is dropped, the body is sent along
171    /// `tx`.
172    ///
173    /// This state is transitioned to `Closed` when an error happens, EOF
174    /// happens, or when trailers are read.
175    Open {
176        body: HyperIncomingBody,
177        tx: oneshot::Sender<StreamEnd>,
178    },
179
180    /// This body is closed and no longer available for reading, no more data
181    /// will come.
182    Closed,
183}
184
185#[async_trait::async_trait]
186impl InputStream for HostIncomingBodyStream {
187    fn read(&mut self, size: usize) -> Result<Bytes, StreamError> {
188        loop {
189            // Handle buffered data/errors if any
190            if !self.buffer.is_empty() {
191                let len = size.min(self.buffer.len());
192                let chunk = self.buffer.split_to(len);
193                return Ok(chunk);
194            }
195
196            if let Some(e) = self.error.take() {
197                return Err(StreamError::LastOperationFailed(e.into()));
198            }
199
200            // Extract the body that we're reading from. If present perform a
201            // non-blocking poll to see if a frame is already here. If it is
202            // then turn the loop again to operate on the results. If it's not
203            // here then return an empty buffer as no data is available at this
204            // time.
205            let body = match &mut self.state {
206                IncomingBodyStreamState::Open { body, .. } => body,
207                IncomingBodyStreamState::Closed => return Err(StreamError::Closed),
208            };
209
210            let future = body.frame();
211            futures::pin_mut!(future);
212            match poll_noop(future) {
213                Some(result) => {
214                    self.record_frame(result);
215                }
216                None => return Ok(Bytes::new()),
217            }
218        }
219    }
220}
221
222#[async_trait::async_trait]
223impl Pollable for HostIncomingBodyStream {
224    async fn ready(&mut self) {
225        if !self.buffer.is_empty() || self.error.is_some() {
226            return;
227        }
228
229        if let IncomingBodyStreamState::Open { body, .. } = &mut self.state {
230            let frame = body.frame().await;
231            self.record_frame(frame);
232        }
233    }
234}
235
236impl Drop for HostIncomingBodyStream {
237    fn drop(&mut self) {
238        // When a body stream is dropped, for whatever reason, attempt to send
239        // the body back to the `tx` which will provide the trailers if desired.
240        // This isn't necessary if the state is already closed. Additionally,
241        // like `record_frame` above, `send` errors are ignored as they indicate
242        // that the body/trailers aren't actually needed.
243        let prev = mem::replace(&mut self.state, IncomingBodyStreamState::Closed);
244        if let IncomingBodyStreamState::Open { body, tx } = prev {
245            let _ = tx.send(StreamEnd::Remaining(body));
246        }
247    }
248}
249
250/// The concrete type behind a `wasi:http/types.future-trailers` resource.
251#[derive(Debug)]
252pub enum HostFutureTrailers {
253    /// Trailers aren't here yet.
254    ///
255    /// This state represents two similar states:
256    ///
257    /// * The body is here and ready for reading and we're waiting to read
258    ///   trailers. This can happen for example when the actual body wasn't read
259    ///   or if the body was only partially read.
260    ///
261    /// * The body is being read by something else and we're waiting for that to
262    ///   send us the trailers (or the body itself). This state will get entered
263    ///   when the body stream is dropped for example. If the body stream reads
264    ///   the trailers itself it will also send a message over here with the
265    ///   trailers.
266    Waiting(HostIncomingBody),
267
268    /// Trailers are ready and here they are.
269    ///
270    /// Note that `Ok(None)` means that there were no trailers for this request
271    /// while `Ok(Some(_))` means that trailers were found in the request.
272    Done(Result<Option<http::HeaderMap>, Arc<Error>>),
273
274    /// Trailers have been consumed by `future-trailers.get`.
275    Consumed,
276}
277
278#[async_trait::async_trait]
279impl Pollable for HostFutureTrailers {
280    async fn ready(&mut self) {
281        let body = match self {
282            HostFutureTrailers::Waiting(body) => body,
283            HostFutureTrailers::Done(_) => return,
284            HostFutureTrailers::Consumed => return,
285        };
286
287        // If the body is itself being read by a body stream then we need to
288        // wait for that to be done.
289        if let IncomingBodyState::InBodyStream(rx) = &mut body.body {
290            match rx.await {
291                // Trailers were read for us and here they are, so store the
292                // result.
293                Ok(StreamEnd::Trailers(Some(t))) => {
294                    *self = Self::Done(Ok(Some(t)));
295                }
296                // The body wasn't fully read and was dropped before trailers
297                // were reached. It's up to us now to complete the body.
298                Ok(StreamEnd::Remaining(b)) => body.body = IncomingBodyState::Start(b),
299
300                // The body itself errored out, so report that through this
301                // future as its contract requires.
302                Ok(StreamEnd::Error(e)) => {
303                    *self = HostFutureTrailers::Done(Err(e));
304                }
305
306                // This means there were no trailers present.
307                Ok(StreamEnd::Trailers(None)) | Err(_) => {
308                    *self = HostFutureTrailers::Done(Ok(None));
309                }
310            }
311        }
312
313        // Here it should be guaranteed that `InBodyStream` is now gone, so if
314        // we have the body ourselves then read frames until trailers are found.
315        let body = match self {
316            HostFutureTrailers::Waiting(body) => body,
317            HostFutureTrailers::Done(_) => return,
318            HostFutureTrailers::Consumed => return,
319        };
320        let hyper_body = match &mut body.body {
321            IncomingBodyState::Start(body) => body,
322            IncomingBodyState::InBodyStream(_) => unreachable!(),
323        };
324        let result = loop {
325            match hyper_body.frame().await {
326                None => break Ok(None),
327                Some(Err(e)) => break Err(Arc::new(e)),
328                Some(Ok(frame)) => {
329                    // If this frame is a data frame ignore it as we're only
330                    // interested in trailers.
331                    if let Ok(header_map) = frame.into_trailers() {
332                        break Ok(Some(header_map));
333                    }
334                }
335            }
336        };
337        *self = HostFutureTrailers::Done(result);
338    }
339}
340
341#[derive(Debug, Clone)]
342struct WrittenState {
343    expected: u64,
344    written: Arc<std::sync::atomic::AtomicU64>,
345}
346
347impl WrittenState {
348    fn new(expected_size: u64) -> Self {
349        Self {
350            expected: expected_size,
351            written: Arc::new(std::sync::atomic::AtomicU64::new(0)),
352        }
353    }
354
355    /// The number of bytes that have been written so far.
356    fn written(&self) -> u64 {
357        self.written.load(std::sync::atomic::Ordering::Relaxed)
358    }
359
360    /// Add `len` to the total number of bytes written. Returns `false` if the new total exceeds
361    /// the number of bytes expected to be written.
362    fn update(&self, len: usize) -> bool {
363        let len = len as u64;
364        let old = self
365            .written
366            .fetch_add(len, std::sync::atomic::Ordering::Relaxed);
367        old + len <= self.expected
368    }
369}
370
371/// The concrete type behind a `wasi:http/types.outgoing-body` resource.
372pub struct HostOutgoingBody {
373    /// The output stream that the body is written to.
374    body_output_stream: Option<Box<dyn OutputStream>>,
375    context: StreamContext,
376    written: Option<WrittenState>,
377    finish_sender: Option<tokio::sync::oneshot::Sender<FinishMessage>>,
378}
379
380impl HostOutgoingBody {
381    /// Create a new `HostOutgoingBody`
382    pub fn new(
383        context: StreamContext,
384        size: Option<u64>,
385        buffer_chunks: usize,
386        chunk_size: usize,
387    ) -> (Self, HyperOutgoingBody) {
388        assert!(buffer_chunks >= 1);
389
390        let written = size.map(WrittenState::new);
391
392        use tokio::sync::oneshot::error::RecvError;
393        struct BodyImpl {
394            body_receiver: mpsc::Receiver<Bytes>,
395            finish_receiver: Option<oneshot::Receiver<FinishMessage>>,
396        }
397        impl Body for BodyImpl {
398            type Data = Bytes;
399            type Error = Error;
400            fn poll_frame(
401                mut self: Pin<&mut Self>,
402                cx: &mut Context<'_>,
403            ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
404                match self.as_mut().body_receiver.poll_recv(cx) {
405                    Poll::Pending => Poll::Pending,
406                    Poll::Ready(Some(frame)) => Poll::Ready(Some(Ok(Frame::data(frame)))),
407
408                    // This means that the `body_sender` end of the channel has been dropped.
409                    Poll::Ready(None) => {
410                        if let Some(mut finish_receiver) = self.as_mut().finish_receiver.take() {
411                            match Pin::new(&mut finish_receiver).poll(cx) {
412                                Poll::Pending => {
413                                    self.as_mut().finish_receiver = Some(finish_receiver);
414                                    Poll::Pending
415                                }
416                                Poll::Ready(Ok(message)) => match message {
417                                    FinishMessage::Finished => Poll::Ready(None),
418                                    FinishMessage::Trailers(trailers) => {
419                                        Poll::Ready(Some(Ok(Frame::trailers(trailers))))
420                                    }
421                                    FinishMessage::Abort => {
422                                        Poll::Ready(Some(Err(Error::HttpProtocolError)))
423                                    }
424                                },
425                                Poll::Ready(Err(RecvError { .. })) => Poll::Ready(None),
426                            }
427                        } else {
428                            Poll::Ready(None)
429                        }
430                    }
431                }
432            }
433        }
434
435        // always add 1 buffer here because one empty slot is required; the
436        // extra slot is part of the clamp since `mpsc::channel` panics above
437        // the semaphore's permit limit.
438        let capacity = channel_capacity(buffer_chunks.saturating_add(1));
439        let (body_sender, body_receiver) = mpsc::channel(capacity);
440        let (finish_sender, finish_receiver) = oneshot::channel();
441        let body_impl = BodyImpl {
442            body_receiver,
443            finish_receiver: Some(finish_receiver),
444        }
445        .boxed_unsync();
446
447        let output_stream = BodyWriteStream::new(context, chunk_size, body_sender, written.clone());
448
449        (
450            Self {
451                body_output_stream: Some(Box::new(output_stream)),
452                context,
453                written,
454                finish_sender: Some(finish_sender),
455            },
456            body_impl,
457        )
458    }
459
460    /// Take the output stream, if it's available.
461    pub fn take_output_stream(&mut self) -> Option<Box<dyn OutputStream>> {
462        self.body_output_stream.take()
463    }
464
465    /// Finish the body, optionally with trailers.
466    pub fn finish(mut self, trailers: Option<FieldMap>) -> Result<(), types::ErrorCode> {
467        // Make sure that the output stream has been dropped, so that the BodyImpl poll function
468        // will immediately pick up the finish sender.
469        drop(self.body_output_stream);
470
471        let sender = self
472            .finish_sender
473            .take()
474            .expect("outgoing-body trailer_sender consumed by a non-owning function");
475
476        if let Some(w) = self.written {
477            let written = w.written();
478            if written != w.expected {
479                let _ = sender.send(FinishMessage::Abort);
480                return Err(self.context.as_body_size_error(written));
481            }
482        }
483
484        let message = if let Some(ts) = trailers {
485            FinishMessage::Trailers(ts.into())
486        } else {
487            FinishMessage::Finished
488        };
489
490        // Ignoring failure: receiver died sending body, but we can't report that here.
491        let _ = sender.send(message);
492
493        Ok(())
494    }
495
496    /// Abort the body.
497    pub fn abort(mut self) {
498        // Make sure that the output stream has been dropped, so that the BodyImpl poll function
499        // will immediately pick up the finish sender.
500        drop(self.body_output_stream);
501
502        let sender = self
503            .finish_sender
504            .take()
505            .expect("outgoing-body trailer_sender consumed by a non-owning function");
506
507        let _ = sender.send(FinishMessage::Abort);
508    }
509}
510
511/// Message sent to end the `[HostOutgoingBody]` stream.
512#[derive(Debug)]
513enum FinishMessage {
514    Finished,
515    Trailers(hyper::HeaderMap),
516    Abort,
517}
518
519/// Whether the body is a request or response body.
520#[derive(Clone, Copy, Debug, Eq, PartialEq)]
521pub enum StreamContext {
522    /// The body is a request body.
523    Request,
524    /// The body is a response body.
525    Response,
526}
527
528impl StreamContext {
529    /// Construct the correct [`types::ErrorCode`] body size error.
530    pub fn as_body_size_error(&self, size: u64) -> types::ErrorCode {
531        match self {
532            StreamContext::Request => types::ErrorCode::HttpRequestBodySize(Some(size)),
533            StreamContext::Response => types::ErrorCode::HttpResponseBodySize(Some(size)),
534        }
535    }
536}
537
538/// Clamp a requested outgoing body channel capacity into the range that
539/// `tokio::sync::mpsc::channel` accepts (it panics outside of it).
540fn channel_capacity(requested: usize) -> usize {
541    requested.clamp(1, tokio::sync::Semaphore::MAX_PERMITS)
542}
543
544/// Provides a [`HostOutputStream`] impl from a [`tokio::sync::mpsc::Sender`].
545#[derive(Debug)]
546struct BodyWriteStream {
547    context: StreamContext,
548    writer: mpsc::Sender<Bytes>,
549    write_budget: usize,
550    written: Option<WrittenState>,
551}
552
553impl BodyWriteStream {
554    /// Create a [`BodyWriteStream`].
555    fn new(
556        context: StreamContext,
557        write_budget: usize,
558        writer: mpsc::Sender<Bytes>,
559        written: Option<WrittenState>,
560    ) -> Self {
561        // at least one capacity is required to send a message
562        assert!(writer.max_capacity() >= 1);
563        BodyWriteStream {
564            context,
565            writer,
566            write_budget,
567            written,
568        }
569    }
570}
571
572#[async_trait::async_trait]
573impl OutputStream for BodyWriteStream {
574    fn write(&mut self, bytes: Bytes) -> Result<(), StreamError> {
575        let len = bytes.len();
576        if len > self.write_budget {
577            return Err(StreamError::Trap(format_err!("write exceeded budget")));
578        }
579        match self.writer.try_send(bytes) {
580            // If the message was sent then it's queued up now in hyper to get
581            // received.
582            Ok(()) => {
583                if let Some(written) = self.written.as_ref() {
584                    if !written.update(len) {
585                        let total = written.written();
586                        return Err(StreamError::LastOperationFailed(format_err!(
587                            self.context.as_body_size_error(total)
588                        )));
589                    }
590                }
591
592                Ok(())
593            }
594
595            // If this channel is full then that means `check_write` wasn't
596            // called. The call to `check_write` always guarantees that there's
597            // at least one capacity if a write is allowed.
598            Err(mpsc::error::TrySendError::Full(_)) => {
599                Err(StreamError::Trap(format_err!("write exceeded budget")))
600            }
601
602            // Hyper is gone so this stream is now closed.
603            Err(mpsc::error::TrySendError::Closed(_)) => Err(StreamError::Closed),
604        }
605    }
606
607    fn flush(&mut self) -> Result<(), StreamError> {
608        // Flushing doesn't happen in this body stream since we're currently
609        // only tracking sending bytes over to hyper.
610        if self.writer.is_closed() {
611            Err(StreamError::Closed)
612        } else {
613            Ok(())
614        }
615    }
616
617    fn check_write(&mut self) -> Result<usize, StreamError> {
618        if self.writer.is_closed() {
619            Err(StreamError::Closed)
620        } else if self.writer.capacity() == 0 {
621            // If there is no more capacity in this sender channel then don't
622            // allow any more writes because the hyper task needs to catch up
623            // now.
624            //
625            // Note that this relies on this task being the only one sending
626            // data to ensure that no one else can steal a write into this
627            // channel.
628            Ok(0)
629        } else {
630            Ok(self.write_budget)
631        }
632    }
633}
634
635#[async_trait::async_trait]
636impl Pollable for BodyWriteStream {
637    async fn ready(&mut self) {
638        // Attempt to perform a reservation for a send. If there's capacity in
639        // the channel or it's already closed then this will return immediately.
640        // If the channel is full this will block until capacity opens up.
641        let _ = self.writer.reserve().await;
642    }
643}
644
645#[cfg(test)]
646mod tests {
647    use super::channel_capacity;
648
649    /// `mpsc::channel` panics above `Semaphore::MAX_PERMITS` and at zero, so the
650    /// capacity handed to it must always be clamped - including for the huge
651    /// values an embedder or `-Shttp-outgoing-body-buffer-chunks` can supply.
652    #[test]
653    fn outgoing_body_channel_capacity_is_always_accepted() {
654        let max = tokio::sync::Semaphore::MAX_PERMITS;
655        for requested in [0, 1, 1024, max, max + 1, usize::MAX] {
656            let capacity = channel_capacity(requested);
657            assert!(
658                capacity >= 1 && capacity <= max,
659                "capacity {capacity} out of range"
660            );
661            // Not panicking is the point: that is what the clamp buys us.
662            let _ = tokio::sync::mpsc::channel::<u8>(capacity);
663        }
664    }
665}