fastly 0.12.0

Fastly Compute API
Documentation
pub(crate) mod handle;

use crate::{
    convert::{Borrowable, ToHeaderName, ToHeaderValue},
    experimental::StreamingBodyExt,
    handle::StreamingBodyHandle,
    http::Body,
};
use http::header::HeaderMap;
use std::io::{BufWriter, Write};

/// A streaming HTTP body that can be written to, or appended to from another body.
///
/// The interface to this type is very similar to `Body`, however it is write-only, and can only be
/// created as a result of calling
/// [`Response::stream_to_client()`][`crate::Response::stream_to_client()`] or
/// [`Request::send_async_streaming()`][`crate::Request::send_async_streaming()`],
/// or as part of a `CandidateResponse` body transformation.
///
/// The most efficient way to write the body is through the [`Write`] implementation. Writes are
/// buffered, and automatically flushed, but you can call [`Write::flush()`] to explicitly flush the
/// buffer and cause a new chunk to be written to the client.
///
/// A streaming body will be automatically aborted if it goes out of scope without calling
/// [`finish()`][`Self::finish()`].
#[must_use = "streaming bodies must be `.finish()`ed"]
pub struct StreamingBody {
    writer: BufWriter<StreamingBodyHandle>,
}

impl StreamingBody {
    /// Finish writing to a streaming body handle.
    pub fn finish(self) -> std::io::Result<()> {
        self.writer
            .into_inner()?
            .finish()
            .map_err(std::io::Error::other)
    }

    /// Abandon a streaming body handle, indicating an unsuccessful end of the stream.
    pub fn abandon(self) -> std::io::Result<()> {
        self.writer
            .into_inner()?
            .abandon()
            .map_err(std::io::Error::other)
    }

    // this is not exported, since misuse can lead to data getting dropped or appearing out of order
    fn handle(&mut self) -> &mut StreamingBodyHandle {
        self.writer.get_mut()
    }

    pub(crate) fn into_handle(self) -> std::io::Result<StreamingBodyHandle> {
        Ok(self.writer.into_inner()?)
    }

    /// Append a body onto the end of this streaming body.
    ///
    #[doc = include_str!("../../../docs/snippets/body-append-constant-time.md")]
    ///
    /// # Examples
    ///
    /// ```no_run
    /// # use fastly::{Body, Response};
    /// # let beresp = Response::new();
    /// # let other_body = Body::new();
    /// let mut streaming_body = beresp.stream_to_client();
    /// streaming_body.append(other_body);
    /// ```
    pub fn append(&mut self, other: Body) {
        // flush the write buffer of the destination body, so that we can use the append method on
        // the underlying handles
        self.writer.flush().expect("fastly_http_body::write failed");
        self.handle().append(other.into_handle())
    }
}

impl From<StreamingBodyHandle> for StreamingBody {
    fn from(handle: StreamingBodyHandle) -> Self {
        Self {
            writer: BufWriter::new(handle),
        }
    }
}

// This trait implementation is much simpler than those of `Body`, since we don't have to manage
// multiple buffers. It's just a passthrough to the methods defined on `BufWriter`.
impl Write for StreamingBody {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.writer.write(buf)
    }

    fn write_vectored(&mut self, bufs: &[std::io::IoSlice<'_>]) -> std::io::Result<usize> {
        self.writer.write_vectored(bufs)
    }

    fn flush(&mut self) -> std::io::Result<()> {
        self.writer.flush()
    }
}

impl StreamingBodyExt for StreamingBody {
    fn append_trailer(&mut self, name: impl ToHeaderName, value: impl ToHeaderValue) {
        self.handle().append_trailer(
            name.into_borrowable().as_ref(),
            value.into_borrowable().as_ref(),
        );
    }

    fn finish_with_trailers(mut self, trailers: &HeaderMap) -> Result<(), std::io::Error> {
        self.writer.get_mut().set_trailers(trailers);
        self.writer
            .into_inner()?
            .finish()
            .map_err(std::io::Error::other)
    }
}