fastly 0.13.0

Fastly Compute API
Documentation
use fastly_sys::fastly_http_req::{PendingResponseKind, SendErrorDetail};

use super::{PendingRequest, Request};
use crate::abi;
use crate::convert::{Borrowable, ToHeaderName, ToHeaderValue};
use crate::handle::{BodyHandle, ResponseHandle};
use crate::http::request::SendErrorCause;

/// A handle to a pending asynchronous request returned by
/// [`RequestHandle::send_async()`][`crate::handle::RequestHandle::send_async()`] or
/// [`RequestHandle::send_async_streaming()`][`crate::handle::RequestHandle::send_async_streaming()`].
///
/// A handle can be evaluated using [`PendingRequestHandle::poll()`],
/// [`PendingRequestHandle::wait()`], or [`select_handles()`][`crate::handle::select_handles()`]. It
/// can also be discarded if the request was sent for effects it might have, and the response is
/// unimportant.
#[derive(Debug)]
#[cfg_attr(target_env = "p1", derive(Eq, Hash, PartialEq))]
#[repr(transparent)]
pub struct PendingRequestHandle {
    handle: u32,
}

impl From<PendingRequest> for PendingRequestHandle {
    fn from(pr: PendingRequest) -> Self {
        // TODO: This is obviously not great, but this is also not an API we really want customers using
        // anyway, and we can clean it up in the next breaking release..
        //
        // Needs more thought.
        pr.pending.into_direct_handle().expect("pending request uses caching features that prevent it from being converted into a PendingRequestHandle")
    }
}

impl PendingRequestHandle {
    pub(crate) const INVALID: Self = Self {
        handle: fastly_shared::INVALID_PENDING_REQUEST_HANDLE,
    };

    pub(crate) fn take(&mut self) -> Self {
        std::mem::replace(self, Self::INVALID)
    }

    pub(crate) fn is_invalid(&self) -> bool {
        self.handle == Self::INVALID.handle
    }

    /// Make a handle from its underlying representation.
    ///
    /// This should only be used when calling the raw ABI directly, and care should be taken not to
    /// reuse or alias handle values.
    #[doc(hidden)]
    pub fn from_u32(handle: u32) -> Self {
        Self { handle }
    }

    /// Get the underlying representation of the handle.
    ///
    /// This should only be used when calling the raw ABI directly, and care should be taken not to
    /// reuse or alias handle values.
    #[cfg_attr(
        not(target_env = "p1"),
        deprecated(
            since = "0.11.6",
            note = "This code will need to be updated for wasip2."
        )
    )]
    pub fn as_u32(&self) -> u32 {
        self.handle
    }

    /// Get a mutable reference to the underlying `u32` representation of the handle.
    ///
    /// This should only be used when calling the raw ABI directly, and care should be taken not to
    /// reuse or alias handle values.
    pub(crate) fn as_u32_mut(&mut self) -> &mut u32 {
        &mut self.handle
    }

    /// Append a header on the `Response` once it is ready, leaving any existing entries of the
    /// same name in place.
    pub fn append_response_header(
        &self,
        name: impl ToHeaderName,
        value: impl ToHeaderValue,
        target: PendingResponseKind,
    ) {
        let name = name.into_borrowable();
        let value = value.into_borrowable();

        let name = name.as_ref().as_str();
        let value = value.as_ref().as_bytes();

        unsafe {
            abi::fastly_http_req::pending_req_header_append(
                self.as_u32(),
                name.as_ptr(),
                name.len(),
                value.as_ptr(),
                value.len(),
                target,
            )
        }
        .result()
        .expect("fastly_http_req::pending_req_header_append failed");
    }

    /// Set a header on the `Response` once it is ready, replacing any existing entries of the same
    /// name.
    pub fn set_response_header(
        &self,
        name: impl ToHeaderName,
        value: impl ToHeaderValue,
        target: PendingResponseKind,
    ) {
        let name = name.into_borrowable();
        let value = value.into_borrowable();

        let name = name.as_ref().as_str();
        let value = value.as_ref().as_bytes();

        unsafe {
            abi::fastly_http_req::pending_req_header_insert(
                self.as_u32(),
                name.as_ptr(),
                name.len(),
                value.as_ptr(),
                value.len(),
                target,
            )
        }
        .result()
        .expect("fastly_http_req::pending_req_header_insert failed");
    }

    /// Remove a header from the `Response` once it is ready.
    pub fn remove_response_header(&self, name: impl ToHeaderName, target: PendingResponseKind) {
        let name = name.into_borrowable();
        let name = name.as_ref().as_str();

        unsafe {
            abi::fastly_http_req::pending_req_header_remove(
                self.as_u32(),
                name.as_ptr(),
                name.len(),
                target,
            )
        }
        .result()
        .expect("fastly_http_req::pending_req_header_remove failed");
    }

    pub(crate) fn with_fastly_cache_miss_headers(self, original_req: &Request) -> Self {
        use crate::http::header::fastly::*;

        self.set_response_header(&X_CACHE, VAL_MISS, PendingResponseKind::Response);
        self.set_response_header(&X_CACHE_HITS, VAL_0, PendingResponseKind::Response);

        if !original_req.response_keeps_surrogate_headers() {
            self.remove_response_header(SURROGATE_KEY, PendingResponseKind::Response);
            self.remove_response_header(SURROGATE_CONTROL, PendingResponseKind::Response);
        }

        self
    }

    /// Move processing of the [PendingRequestHandle] to the background and forward its
    /// eventual response to the downstream client.
    ///
    /// If an error is encountered while sending the request (for example, a
    /// [SendErrorCause::ConnectionRefused] due to the server being unavailable), then
    /// an appropriate 5XX response will be sent instead.
    ///
    /// Using this allows the sandbox to terminate early so that its resources may be freed
    /// up for other incoming requests.
    pub fn send_to_client(self) {
        unsafe { abi::fastly_http_resp::send_downstream_pending(self.as_u32()) }
            .result()
            .expect("fastly_http_resp::send_downstream_pending failed")
    }

    /// Try to get the result of a pending request without blocking.
    ///
    /// This function returns immediately with a [`PollHandleResult`]; if you want to block until a
    /// result is ready, use [`wait()`][`Self::wait()`].
    pub fn poll(self) -> PollHandleResult {
        let mut is_done = -1;
        let mut resp_handle = ResponseHandle::INVALID;
        let mut body_handle = BodyHandle::INVALID;
        let mut error_detail = SendErrorDetail::uninitialized_all();
        let status = unsafe {
            abi::fastly_http_req::pending_req_poll_v2(
                self.as_u32(),
                &mut error_detail,
                &mut is_done,
                resp_handle.as_u32_mut(),
                body_handle.as_u32_mut(),
            )
        };

        // An error indicates either that a handle was invalid or that a request did get polled and
        // had some error for us. For example, we could begin receiving a response that gets
        // truncated early; we might poll it ready with an Incomplete status.
        if status.is_err() {
            return PollHandleResult::Done(Err(SendErrorCause::detail_and_status(
                error_detail,
                Some(status),
            )
            .expect_err("status.is_err()")));
        }

        if is_done < 0 || is_done > 1 {
            // For witx reasons, is_done is indicated by a 0 or 1, rather than a "boolean" type.
            // Getting an out of range value here should be impossible.
            panic!("fastly_http_req_pending_req_poll internal error");
        }
        let is_done = is_done != 0;
        if !is_done {
            return PollHandleResult::Pending(self);
        }
        if is_done && (resp_handle.is_invalid() || body_handle.is_invalid()) {
            // The handles should only be invalid if the status is OK. If the status is an error,
            // that is caught above.
            panic!("fastly_http_req_pending_req_poll internal error");
        } else {
            PollHandleResult::Done(Ok((resp_handle, body_handle)))
        }
    }

    /// Block until the result of a pending request is ready.
    ///
    /// If you want check whether the result is ready without blocking, use
    /// [`poll()`][`Self::poll()`].
    pub fn wait(self) -> Result<(ResponseHandle, BodyHandle), SendErrorCause> {
        let mut resp_handle = ResponseHandle::INVALID;
        let mut body_handle = BodyHandle::INVALID;
        let mut error_detail = SendErrorDetail::uninitialized_all();
        let status = unsafe {
            abi::fastly_http_req::pending_req_wait_v2(
                self.as_u32(),
                &mut error_detail,
                resp_handle.as_u32_mut(),
                body_handle.as_u32_mut(),
            )
        };
        SendErrorCause::detail_and_status(error_detail, Some(status))?;

        if resp_handle.is_invalid() || body_handle.is_invalid() {
            panic!("fastly_http_req::pending_req_wait returned invalid handles");
        }
        Ok((resp_handle, body_handle))
    }
}

/// The result of a call to [`PendingRequestHandle::poll()`].
pub enum PollHandleResult {
    /// The request is still in progress, and can be polled again using the given handle.
    Pending(PendingRequestHandle),
    /// The request has either completed or errored.
    Done(Result<(ResponseHandle, BodyHandle), SendErrorCause>),
}

/// Given a collection of [`PendingRequestHandle`]s, block until the result of one of the handles is
/// ready.
///
/// This function accepts any type which can become an iterator that yields handles; a common choice
/// is `Vec<PendingRequestHandle>`.
///
/// Returns a tuple `(result, index, remaining)`, where:
///
/// - `result` is the result of the handle that became ready.
///
/// - `index` is the index of the handle in the argument collection (e.g., the index of the handle
/// in a vector) that became ready.
///
/// - `remaining` is a vector containing all of the handles that did not become ready. The order of
/// the handles in this vector is not guaranteed to match the order of the handles in the argument
/// collection.
///
/// ### Panics
///
/// Panics if the argument collection is empty, or contains more than
/// [`fastly_shared::MAX_PENDING_REQS`] handles.
pub fn select_handles<I>(
    pending_reqs: I,
) -> (
    Result<(ResponseHandle, BodyHandle), SendErrorCause>,
    usize,
    Vec<PendingRequestHandle>,
)
where
    I: IntoIterator<Item = PendingRequestHandle>,
{
    let mut prs = pending_reqs
        .into_iter()
        .map(|pr| pr.as_u32())
        .collect::<Vec<u32>>();
    if prs.is_empty() || prs.len() > fastly_shared::MAX_PENDING_REQS as usize {
        panic!(
            "the number of selected handles must be at least 1, and less than {}",
            fastly_shared::MAX_PENDING_REQS
        );
    }
    let mut done_index = -1;
    let mut resp_handle = ResponseHandle::INVALID;
    let mut body_handle = BodyHandle::INVALID;
    let mut error_detail = SendErrorDetail::uninitialized_all();

    let status = unsafe {
        abi::fastly_http_req::pending_req_select_v2(
            prs.as_ptr(),
            prs.len(),
            // TODO ACF 2023-08-18: this does nothing; the existing `select` swallows the error that
            // was produced for the "winning" request handle. We'll have to change up the
            // implementation on the host side and maybe come up with a different contract about the
            // relationship between the status and handles. Maybe rely entirely on the details?
            &mut error_detail,
            &mut done_index,
            resp_handle.as_u32_mut(),
            body_handle.as_u32_mut(),
        )
    };

    if status.is_err() || done_index < 0 {
        // since we are providing the out-pointers, and an owned `PendingRequestHandle` in Wasm can
        // only exist if it's present in the host, any error returns from the hostcall would
        // indicate an internal (host) bug. Alternatively, the provided set of handles is empty or
        // beyond MAX_PENDING_REQS, and we want to panic here anyway.
        panic!("fastly_http_req_pending_req_select internal error: {error_detail:?}");
    }

    // If we successfully (status is not an error) waited for a handle, we must have gotten
    // something for a handle we provided. This means `done_index` must be valid.
    let done_index = done_index
        .try_into()
        .expect("fastly_http_req_pending_req_select returned an invalid index");

    // quickly remove the completed handle from the set to return
    prs.swap_remove(done_index);

    let res = if resp_handle.is_invalid() || body_handle.is_invalid() {
        Err(SendErrorCause::detail_and_status(error_detail, None)
            .expect_err("invalid handles but valid status"))
    } else {
        Ok((resp_handle, body_handle))
    };

    (
        res,
        done_index,
        prs.into_iter()
            .map(PendingRequestHandle::from_u32)
            .collect(),
    )
}