Skip to content

Commit

Permalink
dynamic_modules: adds body read/write callbacks (#38102)
Browse files Browse the repository at this point in the history
Commit Message: dynamic_modules: adds body reader/writer functionality
Additional Description:

This adds the following ABI functions:

* envoy_dynamic_module_callback_http_get_request_body_vector_size
* envoy_dynamic_module_callback_http_append_request_body
* envoy_dynamic_module_callback_http_drain_request_body
* envoy_dynamic_module_callback_http_get_response_body_vector
* envoy_dynamic_module_callback_http_get_response_body_vector_size
* envoy_dynamic_module_callback_http_append_response_body
* envoy_dynamic_module_callback_http_drain_response_body

which allows the modules to read and write HTTP body buffers, including
the ability to replace the entire body with a new one.

This completes the initial series of dynamic module feature patches
and after this, the documentation and integration tests will be worked
on.

Risk Level: low
Testing: done
Docs Changes: n/a
Release Notes: n/a
Platform Specific Features: n/a

---------

Signed-off-by: Takeshi Yoneda <[email protected]>
  • Loading branch information
mathetake authored Jan 28, 2025
1 parent c0a1be5 commit 7eaddda
Show file tree
Hide file tree
Showing 10 changed files with 888 additions and 1 deletion.
8 changes: 8 additions & 0 deletions source/extensions/dynamic_modules/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ envoy_cc_library(
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_get_dynamic_metadata_number",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_set_dynamic_metadata_string",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_get_dynamic_metadata_string",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_get_request_body_vector",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_get_request_body_vector_size",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_append_request_body",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_drain_request_body",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_get_response_body_vector",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_get_response_body_vector_size",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_append_response_body",
"-Wl,--export-dynamic-symbol=envoy_dynamic_module_callback_http_drain_response_body",
],
deps = [
":abi_version_lib",
Expand Down
106 changes: 106 additions & 0 deletions source/extensions/dynamic_modules/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ typedef char* envoy_dynamic_module_type_buffer_module_ptr;
*/
typedef char* envoy_dynamic_module_type_buffer_envoy_ptr;

/**
* envoy_dynamic_module_type_envoy_buffer represents a buffer owned by Envoy.
* This is to give the direct access to the buffer in Envoy.
*/
typedef struct {
envoy_dynamic_module_type_buffer_envoy_ptr ptr;
size_t length;
} envoy_dynamic_module_type_envoy_buffer;

/**
* envoy_dynamic_module_type_module_http_header represents a key-value pair of an HTTP header owned
* by the module.
Expand Down Expand Up @@ -639,6 +648,103 @@ void envoy_dynamic_module_callback_http_send_response(
envoy_dynamic_module_type_module_http_header* headers_vector, size_t headers_vector_size,
envoy_dynamic_module_type_buffer_module_ptr body, size_t body_length);

// ------------------- HTTP Request/Response body callbacks --------------------

/**
* envoy_dynamic_module_callback_http_get_request_body_vector is called by the module to get the
* request body as a vector of buffers. The body is returned as an array of
* envoy_dynamic_module_type_envoy_buffer.
*
* PRECONDITION: The module must ensure that the result_buffer_vector is valid and has enough length
* to store all the buffers. The module can use
* envoy_dynamic_module_callback_http_get_request_body_vector_size to get the number of buffers
* before calling this function.
*
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
* corresponding HTTP filter.
* @param result_buffer_vector is the pointer to the array of envoy_dynamic_module_type_envoy_buffer
* where the buffers of the body will be stored. The lifetime of the buffer is guaranteed until the
* end of the current event hook unless the setter callback is called.
* @return true if the body is available, false otherwise.
*/
bool envoy_dynamic_module_callback_http_get_request_body_vector(
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
envoy_dynamic_module_type_envoy_buffer* result_buffer_vector);

/**
* envoy_dynamic_module_callback_http_get_request_body_vector_size is called by the module to get
* the number of buffers in the request body. Combined with
* envoy_dynamic_module_callback_http_get_request_body_vector, this can be used to iterate over all
* buffers in the request body.
*
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
* corresponding HTTP filter.
* @param size is the pointer to the variable where the number of buffers will be stored.
* @return true if the body is available, false otherwise.
*/
bool envoy_dynamic_module_callback_http_get_request_body_vector_size(
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t* size);

/**
* envoy_dynamic_module_callback_http_append_request_body is called by the module to append the
* given data to the end of the request body.
*
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
* corresponding HTTP filter.
* @param data is the pointer to the buffer of the data to be appended.
* @param length is the length of the data.
* @return true if the body is available, false otherwise.
*/
bool envoy_dynamic_module_callback_http_append_request_body(
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
envoy_dynamic_module_type_buffer_module_ptr data, size_t length);

/**
* envoy_dynamic_module_callback_http_drain_request_body is called by the module to drain the given
* number of bytes from the request body. If the number of bytes to drain is greater than
* the size of the body, the whole body will be drained.
*
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
* corresponding HTTP filter.
* @param number_of_bytes is the number of bytes to drain.
* @return true if the body is available, false otherwise.
*/
bool envoy_dynamic_module_callback_http_drain_request_body(
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t number_of_bytes);

/**
* This is the same as envoy_dynamic_module_callback_http_get_request_body_vector, but for the
* response body. See the comments on envoy_dynamic_module_callback_http_get_request_body_vector
* for more details.
*/
bool envoy_dynamic_module_callback_http_get_response_body_vector(
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
envoy_dynamic_module_type_envoy_buffer* result_buffer_vector);

/**
* This is the same as envoy_dynamic_module_callback_http_get_request_body_vector_size, but for the
* response body. See the comments on
* envoy_dynamic_module_callback_http_get_request_body_vector_size for more details.
*/
bool envoy_dynamic_module_callback_http_get_response_body_vector_size(
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t* size);

/**
* This is the same as envoy_dynamic_module_callback_http_append_request_body, but for the response
* body. See the comments on envoy_dynamic_module_callback_http_append_request_body for more
* details.
*/
bool envoy_dynamic_module_callback_http_append_response_body(
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
envoy_dynamic_module_type_buffer_module_ptr data, size_t length);

/**
* This is the same as envoy_dynamic_module_callback_http_drain_request_body, but for the response
* body. See the comments on envoy_dynamic_module_callback_http_drain_request_body for more details.
*/
bool envoy_dynamic_module_callback_http_drain_response_body(
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t number_of_bytes);

// ------------------------ Dynamic Metadata Callbacks -------------------------

/**
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/dynamic_modules/abi_version.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace DynamicModules {
#endif
// This is the ABI version calculated as a sha256 hash of the ABI header files. When the ABI
// changes, this value must change, and the correctness of this value is checked by the test.
const char* kAbiVersion = "271744e1b0090ad28c0006e53e760a445f4ec0a27767d9ac24a1ae87795d8c01";
const char* kAbiVersion = "869dcc448533527a69d9b25dc6e367fb7787e992f9291e71442165b783f19bd1";

#ifdef __cplusplus
} // namespace DynamicModules
Expand Down
10 changes: 10 additions & 0 deletions source/extensions/dynamic_modules/sdk/rust/src/buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ pub struct EnvoyBuffer<'a> {
_marker: std::marker::PhantomData<&'a ()>,
}

impl Default for EnvoyBuffer<'_> {
fn default() -> Self {
Self {
raw_ptr: std::ptr::null(),
length: 0,
_marker: std::marker::PhantomData,
}
}
}

impl EnvoyBuffer<'_> {
/// This is a helper function to create an [`EnvoyBuffer`] from a static string.
///
Expand Down
186 changes: 186 additions & 0 deletions source/extensions/dynamic_modules/sdk/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,114 @@ pub trait EnvoyHttpFilter {
///
/// Returns true if the operation is successful.
fn set_dynamic_metadata_string(&mut self, namespace: &str, key: &str, value: &str) -> bool;

/// Get the currently buffered request body. The body is represented as a list of [`EnvoyBuffer`].
/// Memory contents pointed by each [`EnvoyBuffer`] is mutable and can be modified in place.
/// However, the vector itself is a "copied view". For example, adding or removing
/// [`EnvoyBuffer`] from the vector has no effect on the underlying Envoy buffer. To write beyond
/// the end of the buffer, use [`EnvoyHttpFilter::append_request_body`]. To remove data from the
/// buffer, use [`EnvoyHttpFilter::drain_request_body`].
///
/// To write completely new data, use [`EnvoyHttpFilter::drain_request_body`] for the size of the
/// buffer, and then use [`EnvoyHttpFilter::append_request_body`] to write the new data.
///
/// ```
/// use envoy_proxy_dynamic_modules_rust_sdk::*;
///
/// // This is the test setup.
/// let mut envoy_filter = MockEnvoyHttpFilter::default();
/// envoy_filter
/// .expect_get_request_body()
/// .returning(|| vec![EnvoyBuffer::new("hello"), EnvoyBuffer::new("world")].into());
/// envoy_filter.expect_drain_request_body().return_const(true);
///
///
/// // Calculate the size of the request body in bytes.
/// let buffers = envoy_filter.get_request_body().unwrap();
/// let mut size = 0;
/// for buffer in &buffers {
/// size += buffer.as_slice().len();
/// }
/// assert_eq!(size, 10);
///
/// // drain the entire request body.
/// assert!(envoy_filter.drain_request_body(10));
///
/// // Now start writing new data from the beginning of the request body.
/// ```
///
/// This returns None if the request body is not available.
fn get_request_body<'a>(&'a mut self) -> Option<Vec<EnvoyBuffer<'a>>>;

/// Drain the given number of bytes from the front of the request body.
///
/// Returns false if the request body is not available.
///
/// Note that after changing the request body, it is caller's responsibility to modify the
/// content-length header if necessary.
fn drain_request_body(&mut self, number_of_bytes: usize) -> bool;

/// Append the given data to the end of request body.
///
/// Returns false if the request body is not available.
///
/// Note that after changing the request body, it is caller's responsibility to modify the
/// content-length header if necessary.
fn append_request_body(&mut self, data: &[u8]) -> bool;

/// Get the currently buffered response body. The body is represented as a list of
/// [`EnvoyBuffer`]. Memory contents pointed by each [`EnvoyBuffer`] is mutable and can be
/// modified in place. However, the buffer itself is immutable. For example, adding or removing
/// [`EnvoyBuffer`] from the vector has no effect on the underlying Envoy buffer. To write the
/// contents by changing its length, use [`EnvoyHttpFilter::drain_response_body`] or
/// [`EnvoyHttpFilter::append_response_body`].
///
/// To write completely new data, use [`EnvoyHttpFilter::drain_response_body`] for the size of the
/// buffer, and then use [`EnvoyHttpFilter::append_response_body`] to write the new data.
///
/// ```
/// use envoy_proxy_dynamic_modules_rust_sdk::*;
///
/// // This is the test setup.
/// let mut envoy_filter = MockEnvoyHttpFilter::default();
/// envoy_filter
/// .expect_get_response_body()
/// .returning(|| vec![EnvoyBuffer::new("hello"), EnvoyBuffer::new("world")].into());
/// envoy_filter.expect_drain_response_body().return_const(true);
///
///
/// // Calculate the size of the response body in bytes.
/// let buffers = envoy_filter.get_response_body().unwrap();
/// let mut size = 0;
/// for buffer in &buffers {
/// size += buffer.as_slice().len();
/// }
/// assert_eq!(size, 10);
///
/// // drain the entire response body.
/// assert!(envoy_filter.drain_response_body(10));
///
/// // Now start writing new data from the beginning of the request body.
/// ```
///
/// Returns None if the response body is not available.
fn get_response_body<'a>(&'a mut self) -> Option<Vec<EnvoyBuffer<'a>>>;

/// Drain the given number of bytes from the front of the response body.
///
/// Returns false if the response body is not available.
///
/// Note that after changing the response body, it is caller's responsibility to modify the
/// content-length header if necessary.
fn drain_response_body(&mut self, number_of_bytes: usize) -> bool;

/// Append the given data to the end of the response body.
///
/// Returns false if the response body is not available.
///
/// Note that after changing the response body, it is caller's responsibility to modify the
/// content-length header if necessary.
fn append_response_body(&mut self, data: &[u8]) -> bool;
}

/// This implements the [`EnvoyHttpFilter`] trait with the given raw pointer to the Envoy HTTP
Expand Down Expand Up @@ -631,6 +739,84 @@ impl EnvoyHttpFilter for EnvoyHttpFilterImpl {
)
}
}

fn get_request_body(&mut self) -> Option<Vec<EnvoyBuffer>> {
let mut size: usize = 0;
let ok = unsafe {
abi::envoy_dynamic_module_callback_http_get_request_body_vector_size(self.raw_ptr, &mut size)
};
if !ok || size == 0 {
return None;
}

let buffers: Vec<EnvoyBuffer> = vec![EnvoyBuffer::default(); size];
let success = unsafe {
abi::envoy_dynamic_module_callback_http_get_request_body_vector(
self.raw_ptr,
buffers.as_ptr() as *mut abi::envoy_dynamic_module_type_envoy_buffer,
)
};
if success {
Some(buffers)
} else {
None
}
}

fn drain_request_body(&mut self, number_of_bytes: usize) -> bool {
unsafe {
abi::envoy_dynamic_module_callback_http_drain_request_body(self.raw_ptr, number_of_bytes)
}
}

fn append_request_body(&mut self, data: &[u8]) -> bool {
unsafe {
abi::envoy_dynamic_module_callback_http_append_request_body(
self.raw_ptr,
data.as_ptr() as *const _ as *mut _,
data.len(),
)
}
}

fn get_response_body(&mut self) -> Option<Vec<EnvoyBuffer>> {
let mut size: usize = 0;
let ok = unsafe {
abi::envoy_dynamic_module_callback_http_get_response_body_vector_size(self.raw_ptr, &mut size)
};
if !ok || size == 0 {
return None;
}

let buffers: Vec<EnvoyBuffer> = vec![EnvoyBuffer::default(); size];
let success = unsafe {
abi::envoy_dynamic_module_callback_http_get_response_body_vector(
self.raw_ptr,
buffers.as_ptr() as *mut abi::envoy_dynamic_module_type_envoy_buffer,
)
};
if success {
Some(buffers)
} else {
None
}
}

fn drain_response_body(&mut self, number_of_bytes: usize) -> bool {
unsafe {
abi::envoy_dynamic_module_callback_http_drain_response_body(self.raw_ptr, number_of_bytes)
}
}

fn append_response_body(&mut self, data: &[u8]) -> bool {
unsafe {
abi::envoy_dynamic_module_callback_http_append_response_body(
self.raw_ptr,
data.as_ptr() as *const _ as *mut _,
data.len(),
)
}
}
}

impl EnvoyHttpFilterImpl {
Expand Down
Loading

0 comments on commit 7eaddda

Please sign in to comment.