1use std::{
2 ops::{Bound, RangeBounds},
3 time::Duration,
4};
5
6use derive_builder::Builder;
7
8use crate::{
9 downcast_box::DowncastBox,
10 hostcalls::{self, BufferType, MapType},
11 log_concern,
12 upstream::Upstream,
13 RootContext, Status,
14};
15
16#[derive(Builder)]
18#[builder(setter(into))]
19#[builder(pattern = "owned")]
20#[allow(clippy::type_complexity)]
21pub struct HttpCall<'a> {
22 pub upstream: Upstream<'a>,
24 #[builder(setter(into, each(name = "header")), default)]
27 pub headers: Vec<(&'a str, &'a [u8])>,
28 #[builder(setter(into, each(name = "trailer")), default)]
30 pub trailers: Vec<(&'a str, &'a [u8])>,
31 #[builder(setter(strip_option, into), default)]
33 pub body: Option<&'a [u8]>,
34 #[builder(setter(strip_option, into), default)]
36 pub timeout: Option<Duration>,
37 #[builder(setter(custom), default)]
39 pub callback: Option<Box<dyn FnOnce(&mut DowncastBox<dyn RootContext>, &HttpCallResponse)>>,
40}
41
42impl<'a> HttpCallBuilder<'a> {
43 pub fn callback<R: RootContext + 'static>(
45 mut self,
46 callback: impl FnOnce(&mut R, &HttpCallResponse) + 'static,
47 ) -> Self {
48 self.callback = Some(Some(Box::new(move |root, resp| {
49 callback(
50 root.as_any_mut().downcast_mut().expect("invalid root type"),
51 resp,
52 )
53 })));
54 self
55 }
56}
57
58impl<'a> HttpCall<'a> {
59 const DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
60
61 pub fn dispatch(self) -> Result<(), Status> {
63 let token = hostcalls::dispatch_http_call(
64 &self.upstream.0,
65 &self.headers,
66 self.body,
67 &self.trailers,
68 self.timeout.unwrap_or(Self::DEFAULT_TIMEOUT),
69 )?;
70 if let Some(callback) = self.callback {
71 crate::dispatcher::register_http_callback(token, callback);
72 }
73 Ok(())
74 }
75}
76
77pub struct HttpCallResponse {
79 num_headers: usize,
80 body_size: usize,
81 num_trailers: usize,
82}
83
84impl HttpCallResponse {
85 pub(crate) fn new(num_headers: usize, body_size: usize, num_trailers: usize) -> Self {
86 Self {
87 num_headers,
88 body_size,
89 num_trailers,
90 }
91 }
92
93 pub fn num_headers(&self) -> usize {
95 self.num_headers
96 }
97
98 pub fn num_trailers(&self) -> usize {
100 self.num_trailers
101 }
102
103 pub fn body_size(&self) -> usize {
105 self.body_size
106 }
107
108 pub fn headers(&self) -> Vec<(String, Vec<u8>)> {
110 log_concern(
111 "http-call-headers",
112 hostcalls::get_map(MapType::HttpCallResponseHeaders),
113 )
114 .unwrap_or_default()
115 }
116
117 pub fn header(&self, name: impl AsRef<str>) -> Option<Vec<u8>> {
119 log_concern(
120 "http-call-header",
121 hostcalls::get_map_value(MapType::HttpCallResponseHeaders, name.as_ref()),
122 )
123 }
124
125 pub fn body(&self, range: impl RangeBounds<usize>) -> Option<Vec<u8>> {
127 let start = match range.start_bound() {
128 Bound::Included(x) => *x,
129 Bound::Excluded(x) => x.saturating_sub(1),
130 Bound::Unbounded => 0,
131 };
132 let size = match range.end_bound() {
133 Bound::Included(x) => *x + 1,
134 Bound::Excluded(x) => *x,
135 Bound::Unbounded => self.body_size,
136 }
137 .min(self.body_size)
138 .saturating_sub(start);
139
140 log_concern(
141 "http-call-body",
142 hostcalls::get_buffer(BufferType::HttpCallResponseBody, start, size),
143 )
144 }
145
146 pub fn full_body(&self) -> Option<Vec<u8>> {
148 self.body(..)
149 }
150
151 pub fn trailers(&self) -> Vec<(String, Vec<u8>)> {
153 log_concern(
154 "http-call-trailers",
155 hostcalls::get_map(MapType::HttpCallResponseTrailers),
156 )
157 .unwrap_or_default()
158 }
159
160 pub fn trailer(&self, name: impl AsRef<str>) -> Option<Vec<u8>> {
162 log_concern(
163 "http-call-trailer",
164 hostcalls::get_map_value(MapType::HttpCallResponseTrailers, name.as_ref()),
165 )
166 }
167}