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
99/// The concrete type behind the `wasi:io/streams.input-stream` resource returned
100/// by `wasi:http/types.incoming-body`'s `stream` method.
101#[derive(Debug)]
102pub struct HostIncomingBodyStream {
103    state: IncomingBodyStreamState,
104    buffer: Bytes,
105    error: Option<Error>,
106}
107
108impl HostIncomingBodyStream {
109    fn record_frame(&mut self, frame: Option<Result<Frame<Bytes>, Error>>) {
110        match frame {
111            Some(Ok(frame)) => match frame.into_data() {
112                // A data frame was received, so queue up the buffered data for
113                // the next `read` call.
114                Ok(bytes) => {
115                    assert!(self.buffer.is_empty());
116                    self.buffer = bytes;
117                }
118
119                // Trailers were received meaning that this was the final frame.
120                // Throw away the body and send the trailers along the
121                // `tx` channel to make them available.
122                Err(trailers) => {
123                    let trailers = trailers.into_trailers().unwrap();
124                    let tx = match mem::replace(&mut self.state, IncomingBodyStreamState::Closed) {
125                        IncomingBodyStreamState::Open { body: _, tx } => tx,
126                        IncomingBodyStreamState::Closed => unreachable!(),
127                    };
128
129                    // NB: ignore send failures here because if this fails then
130                    // no one was interested in the trailers.
131                    let _ = tx.send(StreamEnd::Trailers(Some(trailers)));
132                }
133            },
134
135            // An error was received meaning that the stream is now done.
136            // Destroy the body to terminate the stream while enqueueing the
137            // error to get returned from the next call to `read`.
138            Some(Err(e)) => {
139                self.error = Some(e);
140                self.state = IncomingBodyStreamState::Closed;
141            }
142
143            // No more frames are going to be received again, so drop the `body`
144            // and the `tx` channel we'd send the body back onto because it's
145            // not needed as frames are done.
146            None => {
147                self.state = IncomingBodyStreamState::Closed;
148            }
149        }
150    }
151}
152
153#[derive(Debug)]
154enum IncomingBodyStreamState {
155    /// The body is currently open for reading and present here.
156    ///
157    /// When trailers are read, or when this is dropped, the body is sent along
158    /// `tx`.
159    ///
160    /// This state is transitioned to `Closed` when an error happens, EOF
161    /// happens, or when trailers are read.
162    Open {
163        body: HyperIncomingBody,
164        tx: oneshot::Sender<StreamEnd>,
165    },
166
167    /// This body is closed and no longer available for reading, no more data
168    /// will come.
169    Closed,
170}
171
172#[async_trait::async_trait]
173impl InputStream for HostIncomingBodyStream {
174    fn read(&mut self, size: usize) -> Result<Bytes, StreamError> {
175        loop {
176            // Handle buffered data/errors if any
177            if !self.buffer.is_empty() {
178                let len = size.min(self.buffer.len());
179                let chunk = self.buffer.split_to(len);
180                return Ok(chunk);
181            }
182
183            if let Some(e) = self.error.take() {
184                return Err(StreamError::LastOperationFailed(e.into()));
185            }
186
187            // Extract the body that we're reading from. If present perform a
188            // non-blocking poll to see if a frame is already here. If it is
189            // then turn the loop again to operate on the results. If it's not
190            // here then return an empty buffer as no data is available at this
191            // time.
192            let body = match &mut self.state {
193                IncomingBodyStreamState::Open { body, .. } => body,
194                IncomingBodyStreamState::Closed => return Err(StreamError::Closed),
195            };
196
197            let future = body.frame();
198            futures::pin_mut!(future);
199            match poll_noop(future) {
200                Some(result) => {
201                    self.record_frame(result);
202                }
203                None => return Ok(Bytes::new()),
204            }
205        }
206    }
207}
208
209#[async_trait::async_trait]
210impl Pollable for HostIncomingBodyStream {
211    async fn ready(&mut self) {
212        if !self.buffer.is_empty() || self.error.is_some() {
213            return;
214        }
215
216        if let IncomingBodyStreamState::Open { body, .. } = &mut self.state {
217            let frame = body.frame().await;
218            self.record_frame(frame);
219        }
220    }
221}
222
223impl Drop for HostIncomingBodyStream {
224    fn drop(&mut self) {
225        // When a body stream is dropped, for whatever reason, attempt to send
226        // the body back to the `tx` which will provide the trailers if desired.
227        // This isn't necessary if the state is already closed. Additionally,
228        // like `record_frame` above, `send` errors are ignored as they indicate
229        // that the body/trailers aren't actually needed.
230        let prev = mem::replace(&mut self.state, IncomingBodyStreamState::Closed);
231        if let IncomingBodyStreamState::Open { body, tx } = prev {
232            let _ = tx.send(StreamEnd::Remaining(body));
233        }
234    }
235}
236
237/// The concrete type behind a `wasi:http/types.future-trailers` resource.
238#[derive(Debug)]
239pub enum HostFutureTrailers {
240    /// Trailers aren't here yet.
241    ///
242    /// This state represents two similar states:
243    ///
244    /// * The body is here and ready for reading and we're waiting to read
245    ///   trailers. This can happen for example when the actual body wasn't read
246    ///   or if the body was only partially read.
247    ///
248    /// * The body is being read by something else and we're waiting for that to
249    ///   send us the trailers (or the body itself). This state will get entered
250    ///   when the body stream is dropped for example. If the body stream reads
251    ///   the trailers itself it will also send a message over here with the
252    ///   trailers.
253    Waiting(HostIncomingBody),
254
255    /// Trailers are ready and here they are.
256    ///
257    /// Note that `Ok(None)` means that there were no trailers for this request
258    /// while `Ok(Some(_))` means that trailers were found in the request.
259    Done(Result<Option<http::HeaderMap>, Error>),
260
261    /// Trailers have been consumed by `future-trailers.get`.
262    Consumed,
263}
264
265#[async_trait::async_trait]
266impl Pollable for HostFutureTrailers {
267    async fn ready(&mut self) {
268        let body = match self {
269            HostFutureTrailers::Waiting(body) => body,
270            HostFutureTrailers::Done(_) => return,
271            HostFutureTrailers::Consumed => return,
272        };
273
274        // If the body is itself being read by a body stream then we need to
275        // wait for that to be done.
276        if let IncomingBodyState::InBodyStream(rx) = &mut body.body {
277            match rx.await {
278                // Trailers were read for us and here they are, so store the
279                // result.
280                Ok(StreamEnd::Trailers(Some(t))) => {
281                    *self = Self::Done(Ok(Some(t)));
282                }
283                // The body wasn't fully read and was dropped before trailers
284                // were reached. It's up to us now to complete the body.
285                Ok(StreamEnd::Remaining(b)) => body.body = IncomingBodyState::Start(b),
286
287                // This means there were no trailers present.
288                Ok(StreamEnd::Trailers(None)) | Err(_) => {
289                    *self = HostFutureTrailers::Done(Ok(None));
290                }
291            }
292        }
293
294        // Here it should be guaranteed that `InBodyStream` is now gone, so if
295        // we have the body ourselves then read frames until trailers are found.
296        let body = match self {
297            HostFutureTrailers::Waiting(body) => body,
298            HostFutureTrailers::Done(_) => return,
299            HostFutureTrailers::Consumed => return,
300        };
301        let hyper_body = match &mut body.body {
302            IncomingBodyState::Start(body) => body,
303            IncomingBodyState::InBodyStream(_) => unreachable!(),
304        };
305        let result = loop {
306            match hyper_body.frame().await {
307                None => break Ok(None),
308                Some(Err(e)) => break Err(e),
309                Some(Ok(frame)) => {
310                    // If this frame is a data frame ignore it as we're only
311                    // interested in trailers.
312                    if let Ok(header_map) = frame.into_trailers() {
313                        break Ok(Some(header_map));
314                    }
315                }
316            }
317        };
318        *self = HostFutureTrailers::Done(result);
319    }
320}
321
322#[derive(Debug, Clone)]
323struct WrittenState {
324    expected: u64,
325    written: Arc<std::sync::atomic::AtomicU64>,
326}
327
328impl WrittenState {
329    fn new(expected_size: u64) -> Self {
330        Self {
331            expected: expected_size,
332            written: Arc::new(std::sync::atomic::AtomicU64::new(0)),
333        }
334    }
335
336    /// The number of bytes that have been written so far.
337    fn written(&self) -> u64 {
338        self.written.load(std::sync::atomic::Ordering::Relaxed)
339    }
340
341    /// Add `len` to the total number of bytes written. Returns `false` if the new total exceeds
342    /// the number of bytes expected to be written.
343    fn update(&self, len: usize) -> bool {
344        let len = len as u64;
345        let old = self
346            .written
347            .fetch_add(len, std::sync::atomic::Ordering::Relaxed);
348        old + len <= self.expected
349    }
350}
351
352/// The concrete type behind a `wasi:http/types.outgoing-body` resource.
353pub struct HostOutgoingBody {
354    /// The output stream that the body is written to.
355    body_output_stream: Option<Box<dyn OutputStream>>,
356    context: StreamContext,
357    written: Option<WrittenState>,
358    finish_sender: Option<tokio::sync::oneshot::Sender<FinishMessage>>,
359}
360
361impl HostOutgoingBody {
362    /// Create a new `HostOutgoingBody`
363    pub fn new(
364        context: StreamContext,
365        size: Option<u64>,
366        buffer_chunks: usize,
367        chunk_size: usize,
368    ) -> (Self, HyperOutgoingBody) {
369        assert!(buffer_chunks >= 1);
370
371        let written = size.map(WrittenState::new);
372
373        use tokio::sync::oneshot::error::RecvError;
374        struct BodyImpl {
375            body_receiver: mpsc::Receiver<Bytes>,
376            finish_receiver: Option<oneshot::Receiver<FinishMessage>>,
377        }
378        impl Body for BodyImpl {
379            type Data = Bytes;
380            type Error = Error;
381            fn poll_frame(
382                mut self: Pin<&mut Self>,
383                cx: &mut Context<'_>,
384            ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> {
385                match self.as_mut().body_receiver.poll_recv(cx) {
386                    Poll::Pending => Poll::Pending,
387                    Poll::Ready(Some(frame)) => Poll::Ready(Some(Ok(Frame::data(frame)))),
388
389                    // This means that the `body_sender` end of the channel has been dropped.
390                    Poll::Ready(None) => {
391                        if let Some(mut finish_receiver) = self.as_mut().finish_receiver.take() {
392                            match Pin::new(&mut finish_receiver).poll(cx) {
393                                Poll::Pending => {
394                                    self.as_mut().finish_receiver = Some(finish_receiver);
395                                    Poll::Pending
396                                }
397                                Poll::Ready(Ok(message)) => match message {
398                                    FinishMessage::Finished => Poll::Ready(None),
399                                    FinishMessage::Trailers(trailers) => {
400                                        Poll::Ready(Some(Ok(Frame::trailers(trailers))))
401                                    }
402                                    FinishMessage::Abort => {
403                                        Poll::Ready(Some(Err(Error::HttpProtocolError)))
404                                    }
405                                },
406                                Poll::Ready(Err(RecvError { .. })) => Poll::Ready(None),
407                            }
408                        } else {
409                            Poll::Ready(None)
410                        }
411                    }
412                }
413            }
414        }
415
416        // always add 1 buffer here because one empty slot is required
417        let (body_sender, body_receiver) = mpsc::channel(buffer_chunks + 1);
418        let (finish_sender, finish_receiver) = oneshot::channel();
419        let body_impl = BodyImpl {
420            body_receiver,
421            finish_receiver: Some(finish_receiver),
422        }
423        .boxed_unsync();
424
425        let output_stream = BodyWriteStream::new(context, chunk_size, body_sender, written.clone());
426
427        (
428            Self {
429                body_output_stream: Some(Box::new(output_stream)),
430                context,
431                written,
432                finish_sender: Some(finish_sender),
433            },
434            body_impl,
435        )
436    }
437
438    /// Take the output stream, if it's available.
439    pub fn take_output_stream(&mut self) -> Option<Box<dyn OutputStream>> {
440        self.body_output_stream.take()
441    }
442
443    /// Finish the body, optionally with trailers.
444    pub fn finish(mut self, trailers: Option<FieldMap>) -> Result<(), types::ErrorCode> {
445        // Make sure that the output stream has been dropped, so that the BodyImpl poll function
446        // will immediately pick up the finish sender.
447        drop(self.body_output_stream);
448
449        let sender = self
450            .finish_sender
451            .take()
452            .expect("outgoing-body trailer_sender consumed by a non-owning function");
453
454        if let Some(w) = self.written {
455            let written = w.written();
456            if written != w.expected {
457                let _ = sender.send(FinishMessage::Abort);
458                return Err(self.context.as_body_size_error(written));
459            }
460        }
461
462        let message = if let Some(ts) = trailers {
463            FinishMessage::Trailers(ts.into())
464        } else {
465            FinishMessage::Finished
466        };
467
468        // Ignoring failure: receiver died sending body, but we can't report that here.
469        let _ = sender.send(message);
470
471        Ok(())
472    }
473
474    /// Abort the body.
475    pub fn abort(mut self) {
476        // Make sure that the output stream has been dropped, so that the BodyImpl poll function
477        // will immediately pick up the finish sender.
478        drop(self.body_output_stream);
479
480        let sender = self
481            .finish_sender
482            .take()
483            .expect("outgoing-body trailer_sender consumed by a non-owning function");
484
485        let _ = sender.send(FinishMessage::Abort);
486    }
487}
488
489/// Message sent to end the `[HostOutgoingBody]` stream.
490#[derive(Debug)]
491enum FinishMessage {
492    Finished,
493    Trailers(hyper::HeaderMap),
494    Abort,
495}
496
497/// Whether the body is a request or response body.
498#[derive(Clone, Copy, Debug, Eq, PartialEq)]
499pub enum StreamContext {
500    /// The body is a request body.
501    Request,
502    /// The body is a response body.
503    Response,
504}
505
506impl StreamContext {
507    /// Construct the correct [`types::ErrorCode`] body size error.
508    pub fn as_body_size_error(&self, size: u64) -> types::ErrorCode {
509        match self {
510            StreamContext::Request => types::ErrorCode::HttpRequestBodySize(Some(size)),
511            StreamContext::Response => types::ErrorCode::HttpResponseBodySize(Some(size)),
512        }
513    }
514}
515
516/// Provides a [`HostOutputStream`] impl from a [`tokio::sync::mpsc::Sender`].
517#[derive(Debug)]
518struct BodyWriteStream {
519    context: StreamContext,
520    writer: mpsc::Sender<Bytes>,
521    write_budget: usize,
522    written: Option<WrittenState>,
523}
524
525impl BodyWriteStream {
526    /// Create a [`BodyWriteStream`].
527    fn new(
528        context: StreamContext,
529        write_budget: usize,
530        writer: mpsc::Sender<Bytes>,
531        written: Option<WrittenState>,
532    ) -> Self {
533        // at least one capacity is required to send a message
534        assert!(writer.max_capacity() >= 1);
535        BodyWriteStream {
536            context,
537            writer,
538            write_budget,
539            written,
540        }
541    }
542}
543
544#[async_trait::async_trait]
545impl OutputStream for BodyWriteStream {
546    fn write(&mut self, bytes: Bytes) -> Result<(), StreamError> {
547        let len = bytes.len();
548        match self.writer.try_send(bytes) {
549            // If the message was sent then it's queued up now in hyper to get
550            // received.
551            Ok(()) => {
552                if let Some(written) = self.written.as_ref() {
553                    if !written.update(len) {
554                        let total = written.written();
555                        return Err(StreamError::LastOperationFailed(format_err!(
556                            self.context.as_body_size_error(total)
557                        )));
558                    }
559                }
560
561                Ok(())
562            }
563
564            // If this channel is full then that means `check_write` wasn't
565            // called. The call to `check_write` always guarantees that there's
566            // at least one capacity if a write is allowed.
567            Err(mpsc::error::TrySendError::Full(_)) => {
568                Err(StreamError::Trap(format_err!("write exceeded budget")))
569            }
570
571            // Hyper is gone so this stream is now closed.
572            Err(mpsc::error::TrySendError::Closed(_)) => Err(StreamError::Closed),
573        }
574    }
575
576    fn flush(&mut self) -> Result<(), StreamError> {
577        // Flushing doesn't happen in this body stream since we're currently
578        // only tracking sending bytes over to hyper.
579        if self.writer.is_closed() {
580            Err(StreamError::Closed)
581        } else {
582            Ok(())
583        }
584    }
585
586    fn check_write(&mut self) -> Result<usize, StreamError> {
587        if self.writer.is_closed() {
588            Err(StreamError::Closed)
589        } else if self.writer.capacity() == 0 {
590            // If there is no more capacity in this sender channel then don't
591            // allow any more writes because the hyper task needs to catch up
592            // now.
593            //
594            // Note that this relies on this task being the only one sending
595            // data to ensure that no one else can steal a write into this
596            // channel.
597            Ok(0)
598        } else {
599            Ok(self.write_budget)
600        }
601    }
602}
603
604#[async_trait::async_trait]
605impl Pollable for BodyWriteStream {
606    async fn ready(&mut self) {
607        // Attempt to perform a reservation for a send. If there's capacity in
608        // the channel or it's already closed then this will return immediately.
609        // If the channel is full this will block until capacity opens up.
610        let _ = self.writer.reserve().await;
611    }
612}