use super::{FastlyResponseMetadata, Response};
use crate::abi::{self, FastlyStatus, MultiValueHostcallError};
use crate::error::BufferSizeError;
use crate::handle::{BodyHandle, StreamingBodyHandle};
use crate::http::request::SendError;
use bytes::{BufMut, BytesMut};
use http::header::{HeaderName, HeaderValue};
use http::{StatusCode, Version};
use std::convert::TryFrom;
#[allow(unused)]
use crate::handle::RequestHandle;
#[derive(Debug, Eq, Hash, PartialEq)]
#[repr(transparent)]
pub struct ResponseHandle {
pub(crate) handle: u32,
}
impl ResponseHandle {
pub const INVALID: Self = ResponseHandle {
handle: fastly_shared::INVALID_RESPONSE_HANDLE,
};
pub fn is_valid(&self) -> bool {
!self.is_invalid()
}
pub fn is_invalid(&self) -> bool {
self.handle == Self::INVALID.handle
}
pub(crate) fn as_u32(&self) -> u32 {
self.handle
}
pub(crate) fn as_u32_mut(&mut self) -> &mut u32 {
&mut self.handle
}
#[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<'a>(
&'a self,
buf_size: usize,
) -> impl Iterator<Item = Result<HeaderName, BufferSizeError>> + 'a {
self.get_header_names_impl(buf_size, Some(buf_size))
}
pub(crate) fn get_header_names_impl<'a>(
&'a self,
mut initial_buf_size: usize,
max_buf_size: Option<usize>,
) -> impl Iterator<Item = Result<HeaderName, 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 {
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::from_bytes(&name_bytes).unwrap()),
Err(BufferTooSmall { needed_buf_size }) => {
Err(BufferSizeError::new(initial_buf_size, 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::new(initial_buf_size, 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");
}
#[cfg_attr(
feature = "unstable-doc",
doc(include = "../../../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 value = HeaderValue::from_bytes(&buf).expect("bytes from host are valid");
Ok(Some(value))
}
Err(FastlyStatus::INVAL) => Ok(None),
Err(FastlyStatus::BUFLEN) => Err(BufferSizeError::new(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.as_u32(), body.as_u32(), false as u32)
}
.result()
.expect("fastly_http_resp::send_downstream failed");
}
pub fn stream_to_client(self, body: BodyHandle) -> StreamingBodyHandle {
let streaming_body_handle = StreamingBodyHandle::from_body_handle(&body);
let status = unsafe {
abi::fastly_http_resp::send_downstream(self.as_u32(), body.as_u32(), true as u32)
};
status
.result()
.map(|_| streaming_body_handle)
.expect("fastly_http_resp::send_downstream failed")
}
}
pub(crate) fn handles_to_response(
resp_handle: ResponseHandle,
resp_body_handle: BodyHandle,
metadata: FastlyResponseMetadata,
) -> Result<Response, SendError> {
match Response::from_handles(resp_handle, resp_body_handle) {
Ok(mut resp) => {
resp.set_fastly_metadata(metadata);
Ok(resp)
}
Err(e) => Err(SendError::from_resp_metadata(metadata, e)),
}
}