use std::{
ops::{Bound, RangeBounds},
time::Duration,
};
use derive_builder::Builder;
use crate::{
downcast_box::DowncastBox,
hostcalls::{self, BufferType, MapType},
log_concern,
upstream::Upstream,
RootContext, Status,
};
#[derive(Builder)]
#[builder(setter(into))]
#[builder(pattern = "owned")]
#[allow(clippy::type_complexity)]
pub struct HttpCall<'a> {
pub upstream: Upstream<'a>,
#[builder(setter(into, each(name = "header")), default)]
pub headers: Vec<(&'a str, &'a [u8])>,
#[builder(setter(into, each(name = "trailer")), default)]
pub trailers: Vec<(&'a str, &'a [u8])>,
#[builder(setter(strip_option, into), default)]
pub body: Option<&'a [u8]>,
#[builder(setter(strip_option, into), default)]
pub timeout: Option<Duration>,
#[builder(setter(custom), default)]
pub callback: Option<Box<dyn FnOnce(&mut DowncastBox<dyn RootContext>, &HttpCallResponse)>>,
}
impl<'a> HttpCallBuilder<'a> {
pub fn callback<R: RootContext + 'static>(
mut self,
callback: impl FnOnce(&mut R, &HttpCallResponse) + 'static,
) -> Self {
self.callback = Some(Some(Box::new(move |root, resp| {
callback(
root.as_any_mut().downcast_mut().expect("invalid root type"),
resp,
)
})));
self
}
}
impl<'a> HttpCall<'a> {
const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
pub fn dispatch(self) -> Result<(), Status> {
let token = hostcalls::dispatch_http_call(
&self.upstream.0,
&self.headers,
self.body,
&self.trailers,
self.timeout.unwrap_or(Self::DEFAULT_TIMEOUT),
)?;
if let Some(callback) = self.callback {
crate::dispatcher::register_http_callback(token, callback);
}
Ok(())
}
}
pub struct HttpCallResponse {
num_headers: usize,
body_size: usize,
num_trailers: usize,
}
impl HttpCallResponse {
pub(crate) fn new(num_headers: usize, body_size: usize, num_trailers: usize) -> Self {
Self {
num_headers,
body_size,
num_trailers,
}
}
pub fn num_headers(&self) -> usize {
self.num_headers
}
pub fn num_trailers(&self) -> usize {
self.num_trailers
}
pub fn body_size(&self) -> usize {
self.body_size
}
pub fn headers(&self) -> Vec<(String, Vec<u8>)> {
log_concern(
"http-call-headers",
hostcalls::get_map(MapType::HttpCallResponseHeaders),
)
.unwrap_or_default()
}
pub fn header(&self, name: impl AsRef<str>) -> Option<Vec<u8>> {
log_concern(
"http-call-header",
hostcalls::get_map_value(MapType::HttpCallResponseHeaders, name.as_ref()),
)
}
pub fn body(&self, range: impl RangeBounds<usize>) -> Option<Vec<u8>> {
let start = match range.start_bound() {
Bound::Included(x) => *x,
Bound::Excluded(x) => x.saturating_sub(1),
Bound::Unbounded => 0,
};
let size = match range.end_bound() {
Bound::Included(x) => *x + 1,
Bound::Excluded(x) => *x,
Bound::Unbounded => self.body_size,
}
.min(self.body_size)
.saturating_sub(start);
log_concern(
"http-call-body",
hostcalls::get_buffer(BufferType::HttpCallResponseBody, start, size),
)
}
pub fn full_body(&self) -> Option<Vec<u8>> {
self.body(..)
}
pub fn trailers(&self) -> Vec<(String, Vec<u8>)> {
log_concern(
"http-call-trailers",
hostcalls::get_map(MapType::HttpCallResponseTrailers),
)
.unwrap_or_default()
}
pub fn trailer(&self, name: impl AsRef<str>) -> Option<Vec<u8>> {
log_concern(
"http-call-trailer",
hostcalls::get_map_value(MapType::HttpCallResponseTrailers, name.as_ref()),
)
}
}