envoy_sdk/host/http/
client.rs

1// Copyright 2020 Tetrate
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! `Envoy` `HTTP Client API`.
16
17use std::time::Duration;
18
19use crate::host::{self, ByteString, HeaderMap};
20
21pub use crate::abi::proxy_wasm::types::HttpRequestHandle as HttpClientRequestHandle;
22
23/// An interface of the `Envoy` `HTTP Client`.
24///
25/// # Examples
26///
27/// #### Basic usage of [`HttpClient`]:
28///
29/// ```
30/// # use envoy_sdk as envoy;
31/// # use envoy::host::Result;
32/// # fn action() -> Result<()> {
33/// use std::time::Duration;
34/// use envoy::host::HttpClient;
35///
36/// let client = HttpClient::default();
37///
38/// let request_id = client.send_request(
39///     "cluster_name",
40///     &[("header", "value")],
41///     Some(b"request body"),
42///     Some(&[("trailer", "value")]),
43///     Duration::from_secs(5),
44/// )?;
45/// # Ok(())
46/// # }
47/// ```
48///
49/// #### Injecting [`HttpClient`] into a HTTP Filter as a dependency:
50///
51/// ```
52/// # use envoy_sdk as envoy;
53/// use envoy::host::HttpClient;
54///
55/// struct MyHttpFilter<'a> {
56///     http_client: &'a dyn HttpClient,
57/// }
58///
59/// impl<'a> MyHttpFilter<'a> {
60///     /// Creates a new instance parameterized with a given [`HttpClient`] implementation.
61///     pub fn new(http_client: &'a dyn HttpClient) -> Self {
62///         MyHttpFilter { http_client }
63///     }
64///
65///     /// Creates a new instance parameterized with the default [`HttpClient`] implementation.
66///     pub fn default() -> Self {
67///         Self::new(HttpClient::default())
68///     }
69/// }
70/// ```
71///
72/// #### Sending a request and receiving a response inside a `HTTP Filter`:
73///
74/// ```
75/// # use envoy_sdk as envoy;
76/// use std::time::Duration;
77/// use envoy::error::format_err;
78/// use envoy::extension::{HttpFilter, Result};
79/// use envoy::extension::filter::http::{FilterHeadersStatus, RequestHeadersOps, Ops};
80/// use envoy::host::HttpClient;
81/// use envoy::host::http::client::{HttpClientRequestHandle, HttpClientResponseOps};
82///
83/// struct MyHttpFilter<'a> {
84///     http_client: &'a dyn HttpClient,
85///
86///     active_request: Option<HttpClientRequestHandle>,
87/// }
88///
89/// impl<'a> HttpFilter for MyHttpFilter<'a> {
90///     fn on_request_headers(&mut self, _num_headers: usize, _end_of_stream: bool, ops: &dyn RequestHeadersOps) -> Result<FilterHeadersStatus> {
91///         self.http_client.send_request(
92///             "cluster_name",
93///             &[("header", "value")],
94///             Some(b"request body"),
95///             Some(&[("trailer", "value")]),
96///             Duration::from_secs(5),
97///         )?;
98///         Ok(FilterHeadersStatus::StopIteration)  // stop further request processing
99///     }
100///
101///     fn on_http_call_response(
102///        &mut self,
103///        request: HttpClientRequestHandle,
104///        _num_headers: usize,
105///        body_size: usize,
106///        _num_trailers: usize,
107///        filter_ops: &dyn Ops,
108///        http_client_ops: &dyn HttpClientResponseOps,
109///    ) -> Result<()> {
110///        if self.active_request != Some(request) {
111///            // don't use `assert!()` to avoid panicing in production code
112///            return Err(format_err!("received unexpected response from HttpClient"));
113///        }
114///        let response_headers = http_client_ops.http_call_response_headers()?;
115///        let response_body = http_client_ops.http_call_response_body(0, body_size)?;
116/// #      stringify! {
117///        ... look into response headers and response body ...
118/// #      };
119///        filter_ops.resume_request() // resume further request processing
120///    }
121/// }
122/// ```
123///
124/// [`HttpClient`]: trait.HttpClient.html
125pub trait HttpClient {
126    /// Sends an HTTP request asynchronously.
127    ///
128    /// # Arguments
129    ///
130    /// * `upstream` - name of `Envoy` `Cluster` to send request to.
131    /// * `headers`  - request headers
132    /// * `body`     - request body
133    /// * `trailers` - request trailers
134    /// * `timeout`  - request timeout
135    ///
136    /// # Return value
137    ///
138    /// opaque [`identifier`][`HttpClientRequestHandle`] of the request sent. Can be used to correlate requests and responses.
139    ///
140    /// [`HttpClientRequestHandle`]: struct.HttpClientRequestHandle.html
141    fn send_request(
142        &self,
143        upstream: &str,
144        headers: &[(&str, &str)],
145        body: Option<&[u8]>,
146        trailers: Option<&[(&str, &str)]>,
147        timeout: Duration,
148    ) -> host::Result<HttpClientRequestHandle>;
149}
150
151impl dyn HttpClient {
152    /// Returns the default implementation that interacts with `Envoy`
153    /// through its [`ABI`].
154    ///
155    /// [`ABI`]: https://github.com/proxy-wasm/spec
156    pub fn default() -> &'static dyn HttpClient {
157        &impls::Host
158    }
159}
160
161/// An interface for accessing data of the HTTP response received by [`HttpClient`].
162///
163/// [`HttpClient`]: trait.HttpClient.html
164pub trait HttpClientResponseOps {
165    fn http_call_response_headers(&self) -> host::Result<HeaderMap>;
166
167    fn http_call_response_header(&self, name: &str) -> host::Result<Option<ByteString>>;
168
169    fn http_call_response_body(&self, start: usize, max_size: usize) -> host::Result<ByteString>;
170
171    fn http_call_response_trailers(&self) -> host::Result<HeaderMap>;
172
173    fn http_call_response_trailer(&self, name: &str) -> host::Result<Option<ByteString>>;
174}
175
176impl dyn HttpClientResponseOps {
177    /// Returns the default implementation that interacts with `Envoy`
178    /// through its [`ABI`].
179    ///
180    /// [`ABI`]: https://github.com/proxy-wasm/spec
181    pub fn default() -> &'static dyn HttpClientResponseOps {
182        &impls::Host
183    }
184}
185
186mod impls {
187    use std::time::Duration;
188
189    use crate::abi::proxy_wasm::hostcalls;
190    use crate::abi::proxy_wasm::types::{BufferType, MapType};
191
192    use super::{HttpClient, HttpClientRequestHandle, HttpClientResponseOps};
193    use crate::host::{self, ByteString, HeaderMap};
194
195    pub(super) struct Host;
196
197    impl HttpClient for Host {
198        fn send_request(
199            &self,
200            upstream: &str,
201            headers: &[(&str, &str)],
202            body: Option<&[u8]>,
203            trailers: Option<&[(&str, &str)]>,
204            timeout: Duration,
205        ) -> host::Result<HttpClientRequestHandle> {
206            hostcalls::dispatch_http_call(
207                upstream,
208                headers,
209                body,
210                trailers.unwrap_or_default(),
211                timeout,
212            )
213        }
214    }
215
216    impl HttpClientResponseOps for Host {
217        fn http_call_response_headers(&self) -> host::Result<HeaderMap> {
218            hostcalls::get_map(MapType::HttpCallResponseHeaders)
219        }
220
221        fn http_call_response_header(&self, name: &str) -> host::Result<Option<ByteString>> {
222            hostcalls::get_map_value(MapType::HttpCallResponseHeaders, name)
223        }
224
225        fn http_call_response_body(
226            &self,
227            start: usize,
228            max_size: usize,
229        ) -> host::Result<ByteString> {
230            hostcalls::get_buffer(BufferType::HttpCallResponseBody, start, max_size)
231        }
232
233        fn http_call_response_trailers(&self) -> host::Result<HeaderMap> {
234            hostcalls::get_map(MapType::HttpCallResponseTrailers)
235        }
236
237        fn http_call_response_trailer(&self, name: &str) -> host::Result<Option<ByteString>> {
238            hostcalls::get_map_value(MapType::HttpCallResponseTrailers, name)
239        }
240    }
241}