use crate::abi::{self, FastlyStatus, MultiValueHostcallError};
use crate::error::{BufferSizeError, HandleError, HandleKind};
use crate::handle::{BodyHandle, StreamingBodyHandle};
use crate::http::HandleField;
use bytes::{BufMut, BytesMut};
use fastly_shared::{FramingHeadersMode, HttpKeepaliveMode};
use http::header::{HeaderName, HeaderValue};
use http::{StatusCode, Version};
use std::mem::ManuallyDrop;
use std::net::{AddrParseError, Ipv4Addr, Ipv6Addr, SocketAddr};
#[allow(unused)]
use crate::handle::RequestHandle;
#[derive(Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct ResponseHandle {
pub(crate) handle: u32,
}
impl ResponseHandle {
#[cfg_attr(
not(target_env = "p1"),
deprecated(
since = "0.11.6",
note = "This code will need to be updated for wasip2."
)
)]
pub const INVALID: Self = ResponseHandle {
handle: fastly_shared::INVALID_RESPONSE_HANDLE,
};
#[cfg_attr(
not(target_env = "p1"),
deprecated(
since = "0.11.6",
note = "This code will need to be updated for wasip2."
)
)]
pub const fn is_valid(&self) -> bool {
!self.is_invalid()
}
#[cfg_attr(
not(target_env = "p1"),
deprecated(
since = "0.11.6",
note = "This code will need to be updated for wasip2."
)
)]
pub const fn is_invalid(&self) -> bool {
self.handle == fastly_shared::INVALID_RESPONSE_HANDLE
}
#[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
}
#[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_mut(&mut self) -> &mut u32 {
&mut self.handle
}
#[cfg_attr(
not(target_env = "p1"),
deprecated(
since = "0.11.6",
note = "This code will need to be updated for wasip2."
)
)]
pub fn into_u32(self) -> u32 {
ManuallyDrop::new(self).as_u32()
}
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
let mut handle = ResponseHandle::INVALID;
let status = unsafe { abi::fastly_http_resp::new(handle.as_u32_mut()) };
match status.result().map(|_| handle) {
Ok(h) if h.is_valid() => h,
_ => panic!("fastly_http_resp::new failed"),
}
}
pub fn get_header_names(
&self,
buf_size: usize,
) -> impl Iterator<Item = Result<HeaderName, BufferSizeError>> + '_ {
self.get_header_names_impl(buf_size, Some(buf_size))
}
pub(crate) fn get_header_names_impl(
&self,
mut initial_buf_size: usize,
max_buf_size: Option<usize>,
) -> impl Iterator<Item = Result<HeaderName, BufferSizeError>> + '_ {
if let Some(max) = max_buf_size {
initial_buf_size = std::cmp::min(initial_buf_size, max);
}
abi::MultiValueHostcall::new(
b'\0',
initial_buf_size,
max_buf_size,
move |buf, buf_size, cursor, ending_cursor, nwritten| unsafe {
abi::fastly_http_resp::header_names_get(
self.as_u32(),
buf,
buf_size,
cursor,
ending_cursor,
nwritten,
)
},
)
.map(move |res| {
use MultiValueHostcallError::{BufferTooSmall, ClosureError};
match res {
Ok(name_bytes) => Ok(HeaderName::try_from(Vec::from(name_bytes)).unwrap()),
Err(BufferTooSmall { needed_buf_size }) => Err(BufferSizeError::header_name(
max_buf_size
.expect("maximum buffer size must exist if a buffer size error occurs"),
needed_buf_size,
)),
Err(ClosureError(e)) => {
panic!("fastly_http_resp::header_names_get returned error: {:?}", e)
}
}
})
}
pub fn get_header_values<'a>(
&'a self,
name: &'a HeaderName,
max_len: usize,
) -> impl Iterator<Item = Result<HeaderValue, BufferSizeError>> + 'a {
self.get_header_values_impl(name, max_len, Some(max_len))
}
pub(crate) fn get_header_values_impl<'a>(
&'a self,
name: &'a HeaderName,
mut initial_buf_size: usize,
max_buf_size: Option<usize>,
) -> impl Iterator<Item = Result<HeaderValue, BufferSizeError>> + 'a {
if let Some(max) = max_buf_size {
initial_buf_size = std::cmp::min(initial_buf_size, max);
}
abi::MultiValueHostcall::new(
b'\0',
initial_buf_size,
max_buf_size,
move |buf, buf_size, cursor, ending_cursor, nwritten| unsafe {
let name: &[u8] = name.as_ref();
abi::fastly_http_resp::header_values_get(
self.as_u32(),
name.as_ptr(),
name.len(),
buf,
buf_size,
cursor,
ending_cursor,
nwritten,
)
},
)
.map(move |res| {
use MultiValueHostcallError::{BufferTooSmall, ClosureError};
match res {
Ok(value_bytes) => {
let header_value =
unsafe { HeaderValue::from_maybe_shared_unchecked(value_bytes) };
Ok(header_value)
}
Err(BufferTooSmall { needed_buf_size }) => Err(BufferSizeError::header_value(
max_buf_size
.expect("maximum buffer size must exist if a buffer size error occurs"),
needed_buf_size,
)),
Err(ClosureError(e)) => panic!(
"fastly_http_resp::header_values_get returned error: {:?}",
e
),
}
})
}
pub fn set_header_values<'a, I>(&mut self, name: &HeaderName, values: I)
where
I: IntoIterator<Item = &'a HeaderValue>,
{
let mut buf = vec![];
for value in values {
buf.put(value.as_bytes());
buf.put_u8(b'\0');
}
let name: &[u8] = name.as_ref();
unsafe {
abi::fastly_http_resp::header_values_set(
self.as_u32(),
name.as_ptr(),
name.len(),
buf.as_ptr(),
buf.len(),
)
}
.result()
.expect("fastly_http_resp::header_values_set failed");
}
#[doc = include_str!("../../../docs/snippets/handle-get-header-value.md")]
pub fn get_header_value(
&self,
name: &HeaderName,
max_len: usize,
) -> Result<Option<HeaderValue>, BufferSizeError> {
let name: &[u8] = name.as_ref();
let mut buf = BytesMut::with_capacity(max_len);
let mut nwritten = 0;
let status = unsafe {
abi::fastly_http_resp::header_value_get(
self.as_u32(),
name.as_ptr(),
name.len(),
buf.as_mut_ptr(),
buf.capacity(),
&mut nwritten,
)
};
match status.result().map(|_| nwritten) {
Ok(nwritten) => {
assert!(nwritten <= buf.capacity(), "hostcall wrote too many bytes");
unsafe {
buf.set_len(nwritten);
}
let buf = buf.freeze();
let value = unsafe { HeaderValue::from_maybe_shared_unchecked(buf) };
Ok(Some(value))
}
Err(FastlyStatus::INVAL) => Ok(None),
Err(FastlyStatus::BUFLEN) => Err(BufferSizeError::header_value(max_len, nwritten)),
_ => panic!("fastly_http_resp::header_value_get returned error"),
}
}
pub fn insert_header(&mut self, name: &HeaderName, value: &HeaderValue) {
let name_bytes: &[u8] = name.as_ref();
let value_bytes: &[u8] = value.as_ref();
unsafe {
abi::fastly_http_resp::header_insert(
self.as_u32(),
name_bytes.as_ptr(),
name_bytes.len(),
value_bytes.as_ptr(),
value_bytes.len(),
)
}
.result()
.expect("fastly_http_resp::header_insert returned error");
}
pub fn append_header(&mut self, name: &HeaderName, value: &HeaderValue) {
let name_bytes: &[u8] = name.as_ref();
let value_bytes: &[u8] = value.as_ref();
unsafe {
abi::fastly_http_resp::header_append(
self.as_u32(),
name_bytes.as_ptr(),
name_bytes.len(),
value_bytes.as_ptr(),
value_bytes.len(),
)
}
.result()
.expect("fastly_http_resp::header_append returned error");
}
pub fn remove_header(&mut self, name: &HeaderName) -> bool {
let name_bytes: &[u8] = name.as_ref();
let status = unsafe {
abi::fastly_http_resp::header_remove(
self.as_u32(),
name_bytes.as_ptr(),
name_bytes.len(),
)
};
match status.result() {
Ok(_) => true,
Err(FastlyStatus::INVAL) => false,
_ => panic!("fastly_http_resp::header_remove returned error"),
}
}
pub fn set_status(&mut self, status: StatusCode) {
unsafe { abi::fastly_http_resp::status_set(self.as_u32(), status.as_u16()) }
.result()
.expect("fastly_http_resp::status_set returned error")
}
pub fn get_status(&self) -> StatusCode {
let mut status = 0;
let fastly_status =
unsafe { abi::fastly_http_resp::status_get(self.as_u32(), &mut status) };
match fastly_status.result().map(|_| status) {
Ok(status) => StatusCode::from_u16(status).expect("invalid http status"),
_ => panic!("fastly_http_resp::status_get failed"),
}
}
pub fn get_version(&self) -> Version {
let mut version = 0;
let status = unsafe { abi::fastly_http_resp::version_get(self.as_u32(), &mut version) };
if status.is_err() {
panic!("fastly_http_resp::version_get failed");
} else {
abi::HttpVersion::try_from(version)
.map(Into::into)
.expect("invalid http version")
}
}
pub fn set_version(&mut self, v: Version) {
unsafe {
abi::fastly_http_resp::version_set(self.as_u32(), abi::HttpVersion::from(v) as u32)
}
.result()
.expect("fastly_http_resp::version_get failed");
}
pub fn send_to_client(self, body: BodyHandle) {
unsafe {
abi::fastly_http_resp::send_downstream(self.into_u32(), body.into_u32(), false as u32)
}
.result()
.expect("fastly_http_resp::send_downstream failed");
}
pub fn stream_to_client(self, body: BodyHandle) -> StreamingBodyHandle {
let status = unsafe {
abi::fastly_http_resp::send_downstream(self.into_u32(), body.as_u32(), true as u32)
};
let streaming_body_handle = StreamingBodyHandle::from_body_handle(body);
status
.result()
.map(|_| streaming_body_handle)
.expect("fastly_http_resp::send_downstream failed")
}
pub fn set_framing_headers_mode(&mut self, mode: FramingHeadersMode) {
unsafe { abi::fastly_http_resp::framing_headers_mode_set(self.as_u32(), mode) }
.result()
.expect("fastly_http_resp::framing_headers_mode_set failed")
}
#[doc(hidden)]
pub fn set_http_keepalive_mode(&mut self, mode: HttpKeepaliveMode) -> Result<(), FastlyStatus> {
unsafe { abi::fastly_http_resp::http_keepalive_mode_set(self.as_u32(), mode) }.result()
}
pub fn close(self) -> Result<(), HandleError> {
match unsafe { abi::fastly_http_resp::close(self.as_u32()) } {
FastlyStatus::OK => Ok(()),
_ => Err(HandleError::ClosedHandle(HandleKind::Response)),
}
}
pub fn remote_addr(&self) -> Result<Option<SocketAddr>, AddrParseError> {
let mut octets = [0u8; 16];
let mut port = 0u16;
let mut nwritten = 0;
let status = unsafe { abi::fastly_http_resp::get_addr_dest_port(self.as_u32(), &mut port) };
match status {
FastlyStatus::NONE => return Ok(None),
FastlyStatus::OK => (),
e => panic!("couldn't get the backend port: e = {e:?}"),
}
let status = unsafe {
abi::fastly_http_resp::get_addr_dest_ip(
self.as_u32(),
octets.as_mut_ptr(),
&mut nwritten,
)
};
match (status, nwritten) {
(FastlyStatus::NONE, _) => return Ok(None),
(FastlyStatus::OK, 4) => {
let octets: [u8; 4] = octets[0..4]
.try_into()
.expect("octets is at least 4 bytes long");
Ok(Some((Ipv4Addr::from(octets), port).into()))
}
(FastlyStatus::OK, 16) => Ok(Some((Ipv6Addr::from(octets), port).into())),
(e, n) => {
panic!("couldn't get the backend ip address: e = {e:?}, n = {n:?}");
}
}
}
}
impl Drop for ResponseHandle {
fn drop(&mut self) {
if self.is_valid() {
unsafe { abi::fastly_http_resp::close(self.as_u32()) }
.result()
.expect("fastly_http_resp::close failed");
}
}
}
impl HandleField<ResponseHandle> for Version {
fn load(handle: &ResponseHandle) -> Self {
handle.get_version()
}
fn store(&self, handle: &mut ResponseHandle) {
handle.set_version(*self)
}
}
impl HandleField<ResponseHandle> for StatusCode {
fn load(handle: &ResponseHandle) -> Self {
handle.get_status()
}
fn store(&self, handle: &mut ResponseHandle) {
handle.set_status(*self)
}
}