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}