Skip to content

Commit 4f974bd

Browse files
authored
dynamic_modules: support for http callouts (envoyproxy#39151)
Commit Message: dynamic_modules: support for http callouts Additional Description: This adds support for http callouts initiated from HTTP filter context. More precisely, this adds a new ABI function: * envoy_dynamic_module_callback_http_filter_http_callout * envoy_dynamic_module_on_http_filter_http_callout_done Risk Level: low Testing: unit+integration Docs Changes: n/a Release Notes: n/a Platform Specific Features: n/a --------- Signed-off-by: Takeshi Yoneda <[email protected]>
1 parent adf5a8d commit 4f974bd

File tree

15 files changed

+766
-50
lines changed

15 files changed

+766
-50
lines changed

source/extensions/dynamic_modules/abi.h

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,34 @@ typedef enum {
371371
envoy_dynamic_module_type_attribute_id_XdsFilterChainName,
372372
} envoy_dynamic_module_type_attribute_id;
373373

374+
/**
375+
* envoy_dynamic_module_type_http_callout_init_result represents the result of the HTTP callout
376+
* initialization after envoy_dynamic_module_callback_http_filter_http_callout is called.
377+
* Success means the callout is successfully initialized and ready to be used.
378+
* MissingRequiredHeaders means the callout is missing one of the required headers, :path, :method,
379+
* or host header. DuplicateCalloutId means the callout id is already used by another callout.
380+
* ClusterNotFound means the cluster is not found in the configuration. CannotCreateRequest means
381+
* the request cannot be created. That happens when, for example, there's no healthy upstream host
382+
* in the cluster.
383+
*/
384+
typedef enum {
385+
envoy_dynamic_module_type_http_callout_init_result_Success,
386+
envoy_dynamic_module_type_http_callout_init_result_MissingRequiredHeaders,
387+
envoy_dynamic_module_type_http_callout_init_result_ClusterNotFound,
388+
envoy_dynamic_module_type_http_callout_init_result_DuplicateCalloutId,
389+
envoy_dynamic_module_type_http_callout_init_result_CannotCreateRequest,
390+
} envoy_dynamic_module_type_http_callout_init_result;
391+
392+
/**
393+
* envoy_dynamic_module_type_http_callout_result represents the result of the HTTP callout.
394+
* This corresponds to `AsyncClient::FailureReason::*` in envoy/http/async_client.h plus Success.
395+
*/
396+
typedef enum {
397+
envoy_dynamic_module_type_http_callout_result_Success,
398+
envoy_dynamic_module_type_http_callout_result_Reset,
399+
envoy_dynamic_module_type_http_callout_result_ExceedResponseBufferLimit,
400+
} envoy_dynamic_module_type_http_callout_result;
401+
374402
// -----------------------------------------------------------------------------
375403
// ------------------------------- Event Hooks ---------------------------------
376404
// -----------------------------------------------------------------------------
@@ -566,6 +594,32 @@ void envoy_dynamic_module_on_http_filter_stream_complete(
566594
void envoy_dynamic_module_on_http_filter_destroy(
567595
envoy_dynamic_module_type_http_filter_module_ptr filter_module_ptr);
568596

597+
/**
598+
* envoy_dynamic_module_on_http_filter_http_callout_done is called when the HTTP callout
599+
* response is received initiated by a HTTP filter.
600+
*
601+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
602+
* corresponding HTTP filter.
603+
* @param filter_module_ptr is the pointer to the in-module HTTP filter created by
604+
* envoy_dynamic_module_on_http_filter_new.
605+
* @param callout_id is the ID of the callout. This is used to differentiate between multiple
606+
* calls.
607+
* @param result is the result of the callout.
608+
* @param headers is the headers of the response.
609+
* @param headers_size is the size of the headers.
610+
* @param body_vector is the body of the response.
611+
* @param body_vector_size is the size of the body.
612+
*
613+
* headers and body_vector are owned by Envoy, and they are guaranteed to be valid until the end of
614+
* this event hook. They may be null if the callout fails or the response is empty.
615+
*/
616+
void envoy_dynamic_module_on_http_filter_http_callout_done(
617+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
618+
envoy_dynamic_module_type_http_filter_module_ptr filter_module_ptr, uint32_t callout_id,
619+
envoy_dynamic_module_type_http_callout_result result,
620+
envoy_dynamic_module_type_http_header* headers, size_t headers_size,
621+
envoy_dynamic_module_type_envoy_buffer* body_vector, size_t body_vector_size);
622+
569623
// -----------------------------------------------------------------------------
570624
// -------------------------------- Callbacks ----------------------------------
571625
// -----------------------------------------------------------------------------
@@ -1083,6 +1137,32 @@ bool envoy_dynamic_module_callback_http_filter_get_attribute_int(
10831137
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
10841138
envoy_dynamic_module_type_attribute_id attribute_id, uint64_t* result);
10851139

1140+
/**
1141+
* envoy_dynamic_module_callback_http_filter_http_callout is called by the module to initiate
1142+
* an HTTP callout. The callout is initiated by the HTTP filter and the response is received in
1143+
* envoy_dynamic_module_on_http_filter_http_callout_done.
1144+
*
1145+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
1146+
* corresponding HTTP filter.
1147+
* @param callout_id is the ID of the callout. This can be arbitrary and is used to
1148+
* differentiate between multiple calls from the same filter.
1149+
* @param cluster_name is the name of the cluster to which the callout is sent.
1150+
* @param cluster_name_length is the length of the cluster name.
1151+
* @param headers is the headers of the request. It must contain :method, :path and host headers.
1152+
* @param headers_size is the size of the headers.
1153+
* @param body is the pointer to the buffer of the body of the request.
1154+
* @param body_size is the length of the body.
1155+
* @param timeout_milliseconds is the timeout for the callout in milliseconds.
1156+
* @return envoy_dynamic_module_type_http_callout_init_result is the result of the callout.
1157+
*/
1158+
envoy_dynamic_module_type_http_callout_init_result
1159+
envoy_dynamic_module_callback_http_filter_http_callout(
1160+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, uint32_t callout_id,
1161+
envoy_dynamic_module_type_buffer_module_ptr cluster_name, size_t cluster_name_length,
1162+
envoy_dynamic_module_type_http_header* headers, size_t headers_size,
1163+
envoy_dynamic_module_type_buffer_module_ptr body, size_t body_size,
1164+
uint64_t timeout_milliseconds);
1165+
10861166
#ifdef __cplusplus
10871167
}
10881168
#endif

source/extensions/dynamic_modules/abi_version.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace DynamicModules {
66
#endif
77
// This is the ABI version calculated as a sha256 hash of the ABI header files. When the ABI
88
// changes, this value must change, and the correctness of this value is checked by the test.
9-
const char* kAbiVersion = "0874b1e9587ef1dbd355ffde32f3caf424cb819df552de4833b2ed5b8996c18b";
9+
const char* kAbiVersion = "915695bafb13d22d4f0bdb3b9a249193532779eb8a7dc7d72b48ecc1c9fe943e";
1010

1111
#ifdef __cplusplus
1212
} // namespace DynamicModules

source/extensions/dynamic_modules/sdk/rust/src/lib.rs

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,23 @@ pub trait HttpFilter<EHF: EnvoyHttpFilter> {
207207
///
208208
/// This is called before this [`HttpFilter`] object is dropped and access logs are flushed.
209209
fn on_stream_complete(&mut self, _envoy_filter: &mut EHF) {}
210+
211+
/// This is called when the HTTP callout is done.
212+
///
213+
/// * `envoy_filter` can be used to interact with the underlying Envoy filter object.
214+
/// * `callout_id` is the ID of the callout that was done.
215+
/// * `result` indicates the result of the callout.
216+
/// * `response_headers` is a list of key-value pairs of the response headers. This is optional.
217+
/// * `response_body` is the response body. This is optional.
218+
fn on_http_callout_done(
219+
&mut self,
220+
_envoy_filter: &mut EHF,
221+
_callout_id: u32,
222+
_result: abi::envoy_dynamic_module_type_http_callout_result,
223+
_response_headers: Option<&[(EnvoyBuffer, EnvoyBuffer)]>,
224+
_response_body: Option<&[EnvoyBuffer]>,
225+
) {
226+
}
210227
}
211228

212229
/// An opaque object that represents the underlying Envoy Http filter config. This has one to one
@@ -533,6 +550,32 @@ pub trait EnvoyHttpFilter {
533550
&self,
534551
attribute_id: abi::envoy_dynamic_module_type_attribute_id,
535552
) -> Option<i64>;
553+
554+
/// Send an HTTP callout to the given cluster with the given headers and body.
555+
/// Multiple callouts can be made from the same filter. Different callouts can be
556+
/// distinguished by the `callout_id` parameter.
557+
///
558+
/// Headers must contain the `:method`, ":path", and `host` headers.
559+
///
560+
/// This returns the status of the callout. The meaning of the status is
561+
///
562+
/// * Success: The callout was sent successfully.
563+
/// * MissingRequiredHeaders: One of the required headers is missing: `:method`, `:path`, or
564+
/// `host`.
565+
/// * ClusterNotFound: The cluster with the given name was not found.
566+
/// * DuplicateCalloutId: The callout ID is already in use.
567+
/// * CouldNotCreateRequest: The request could not be created. This happens when, for example,
568+
/// there's no healthy upstream host in the cluster.
569+
///
570+
/// The callout result will be delivered to the [`HttpFilter::on_http_callout_done`] method.
571+
fn send_http_callout<'a>(
572+
&mut self,
573+
_callout_id: u32,
574+
_cluster_name: &'a str,
575+
_headers: Vec<(&'a str, &'a [u8])>,
576+
_body: Option<&'a [u8]>,
577+
_timeout_milliseconds: u64,
578+
) -> abi::envoy_dynamic_module_type_http_callout_init_result;
536579
}
537580

538581
/// This implements the [`EnvoyHttpFilter`] trait with the given raw pointer to the Envoy HTTP
@@ -1016,6 +1059,32 @@ impl EnvoyHttpFilter for EnvoyHttpFilterImpl {
10161059
None
10171060
}
10181061
}
1062+
1063+
fn send_http_callout<'a>(
1064+
&mut self,
1065+
callout_id: u32,
1066+
cluster_name: &'a str,
1067+
headers: Vec<(&'a str, &'a [u8])>,
1068+
body: Option<&'a [u8]>,
1069+
timeout_milliseconds: u64,
1070+
) -> abi::envoy_dynamic_module_type_http_callout_init_result {
1071+
let body_ptr = body.map(|s| s.as_ptr()).unwrap_or(std::ptr::null());
1072+
let body_length = body.map(|s| s.len()).unwrap_or(0);
1073+
let headers_ptr = headers.as_ptr() as *const abi::envoy_dynamic_module_type_module_http_header;
1074+
unsafe {
1075+
abi::envoy_dynamic_module_callback_http_filter_http_callout(
1076+
self.raw_ptr,
1077+
callout_id,
1078+
cluster_name.as_ptr() as *const _ as *mut _,
1079+
cluster_name.len(),
1080+
headers_ptr as *const _ as *mut _,
1081+
headers.len(),
1082+
body_ptr as *const _ as *mut _,
1083+
body_length,
1084+
timeout_milliseconds,
1085+
)
1086+
}
1087+
}
10191088
}
10201089

10211090
impl EnvoyHttpFilterImpl {
@@ -1341,3 +1410,37 @@ unsafe extern "C" fn envoy_dynamic_module_on_http_filter_response_trailers(
13411410
let filter = &mut **filter;
13421411
filter.on_response_trailers(&mut EnvoyHttpFilterImpl::new(envoy_ptr))
13431412
}
1413+
1414+
#[no_mangle]
1415+
unsafe extern "C" fn envoy_dynamic_module_on_http_filter_http_callout_done(
1416+
envoy_ptr: abi::envoy_dynamic_module_type_http_filter_envoy_ptr,
1417+
filter_ptr: abi::envoy_dynamic_module_type_http_filter_module_ptr,
1418+
callout_id: u32,
1419+
result: abi::envoy_dynamic_module_type_http_callout_result,
1420+
headers: *const abi::envoy_dynamic_module_type_http_header,
1421+
headers_size: usize,
1422+
body_vector: *const abi::envoy_dynamic_module_type_envoy_buffer,
1423+
body_vector_size: usize,
1424+
) {
1425+
let filter = filter_ptr as *mut *mut dyn HttpFilter<EnvoyHttpFilterImpl>;
1426+
let filter = &mut **filter;
1427+
let headers = if headers_size > 0 {
1428+
Some(unsafe {
1429+
std::slice::from_raw_parts(headers as *const (EnvoyBuffer, EnvoyBuffer), headers_size)
1430+
})
1431+
} else {
1432+
None
1433+
};
1434+
let body = if body_vector_size > 0 {
1435+
Some(unsafe { std::slice::from_raw_parts(body_vector as *const EnvoyBuffer, body_vector_size) })
1436+
} else {
1437+
None
1438+
};
1439+
filter.on_http_callout_done(
1440+
&mut EnvoyHttpFilterImpl::new(envoy_ptr),
1441+
callout_id,
1442+
result,
1443+
headers,
1444+
body,
1445+
)
1446+
}

source/extensions/filters/http/dynamic_modules/abi_impl.cc

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
#include "source/common/http/header_map_impl.h"
2+
#include "source/common/http/message_impl.h"
13
#include "source/common/http/utility.h"
24
#include "source/common/router/string_accessor_impl.h"
35
#include "source/extensions/dynamic_modules/abi.h"
@@ -713,6 +715,41 @@ bool envoy_dynamic_module_callback_http_filter_get_attribute_int(
713715
}
714716
return ok;
715717
}
718+
719+
envoy_dynamic_module_type_http_callout_init_result
720+
envoy_dynamic_module_callback_http_filter_http_callout(
721+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, uint32_t callout_id,
722+
envoy_dynamic_module_type_buffer_module_ptr cluster_name, size_t cluster_name_length,
723+
envoy_dynamic_module_type_http_header* headers, size_t headers_size,
724+
envoy_dynamic_module_type_buffer_module_ptr body, size_t body_size,
725+
uint64_t timeout_milliseconds) {
726+
auto filter = static_cast<DynamicModuleHttpFilter*>(filter_envoy_ptr);
727+
728+
// Try to get the cluster from the cluster manager for the given cluster name.
729+
absl::string_view cluster_name_view(cluster_name, cluster_name_length);
730+
731+
// Construct the request message, starting with the headers, checking for required headers, and
732+
// adding the body if present.
733+
std::unique_ptr<RequestHeaderMapImpl> hdrs = Http::RequestHeaderMapImpl::create();
734+
for (size_t i = 0; i < headers_size; i++) {
735+
const auto& header = &headers[i];
736+
const absl::string_view key(static_cast<const char*>(header->key_ptr), header->key_length);
737+
const absl::string_view value(static_cast<const char*>(header->value_ptr),
738+
header->value_length);
739+
hdrs->addCopy(Http::LowerCaseString(key), value);
740+
}
741+
Http::RequestMessagePtr message(new Http::RequestMessageImpl(std::move(hdrs)));
742+
if (message->headers().Path() == nullptr || message->headers().Method() == nullptr ||
743+
message->headers().Host() == nullptr) {
744+
return envoy_dynamic_module_type_http_callout_init_result_MissingRequiredHeaders;
745+
}
746+
if (body_size > 0) {
747+
message->body().add(absl::string_view(static_cast<const char*>(body), body_size));
748+
message->headers().setContentLength(body_size);
749+
}
750+
return filter->sendHttpCallout(callout_id, cluster_name_view, std::move(message),
751+
timeout_milliseconds);
752+
}
716753
}
717754
} // namespace HttpFilters
718755
} // namespace DynamicModules

source/extensions/filters/http/dynamic_modules/factory.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ absl::StatusOr<Http::FilterFactoryCb> DynamicModuleConfigFactory::createFilterFa
3232
Envoy::Extensions::DynamicModules::HttpFilters::DynamicModuleHttpFilterConfigSharedPtr>
3333
filter_config =
3434
Envoy::Extensions::DynamicModules::HttpFilters::newDynamicModuleHttpFilterConfig(
35-
proto_config.filter_name(), config, std::move(dynamic_module.value()));
35+
proto_config.filter_name(), config, std::move(dynamic_module.value()), context);
3636

3737
if (!filter_config.ok()) {
3838
return absl::InvalidArgumentError("Failed to create filter config: " +

0 commit comments

Comments
 (0)