diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2a33eb492c5d8..5efe747e3aa0f 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -14,6 +14,11 @@ behavior_changes: hardware thread count and CPU affinity for worker thread calculation. Uses conservative rounding (floor) to account for non-worker threads and prevent container throttling, which may reduce the total number of connections. +- area: dynamic modules + change: | + The dynamic module ABI has been updated to support streaming body manipulation. This change also + fixed potential incorrect behavior when access or modify the request or response body. See + https://github.com/envoyproxy/envoy/issues/40918 for more details. minor_behavior_changes: # *Changes that may cause incompatibilities for some users, but should not for most* diff --git a/source/extensions/dynamic_modules/abi.h b/source/extensions/dynamic_modules/abi.h index 4143baf671277..8af72f9a2d927 100644 --- a/source/extensions/dynamic_modules/abi.h +++ b/source/extensions/dynamic_modules/abi.h @@ -1378,14 +1378,53 @@ void envoy_dynamic_module_callback_http_send_response_trailers( // ------------------- 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 + * NOTE: Envoy will handle the request/response as a stream of data. Therefore, the body may not be + * available in its entirety before the end of stream flag is set. The Envoy will provides both the + * received body (body pieces received in the latest event) and the buffered body (body pieces + * buffered so far) to the module. The module should be aware of this distinction when processing + * the body. + * + * NOTE: The received body could only be available during the request/response body + * event hooks (the envoy_dynamic_module_on_http_filter_request_body and + * envoy_dynamic_module_on_http_filter_response_body). + * Outside of these hooks, the received body will be unavailable. + * + * NOTE: The buffered body, however, is always available. But only the latest data processing filter + * in the filter chain could modify the buffered body. That is say for a given filter X, filter X + * can safely modify the buffered body if and only if the filters following filter X in the filter + * chain have not yet accessed the body. + */ + +/** + * envoy_dynamic_module_callback_http_get_received_request_body_vector is called by the module to + * get the current 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_received_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_received_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_buffered_request_body_vector is called by the module to + * get the buffered 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. + * envoy_dynamic_module_callback_http_get_buffered_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. @@ -1394,27 +1433,41 @@ void envoy_dynamic_module_callback_http_send_response_trailers( * 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( +bool envoy_dynamic_module_callback_http_get_buffered_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. + * envoy_dynamic_module_callback_http_get_received_request_body_vector_size is called by the module + * to get the number of buffers in the current request body. Combined with + * envoy_dynamic_module_callback_http_get_received_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( +bool envoy_dynamic_module_callback_http_get_received_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. + * envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size is called by the module + * to get the number of buffers in the buffered request body. Combined with + * envoy_dynamic_module_callback_http_get_buffered_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_buffered_request_body_vector_size( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t* size); + +/** + * envoy_dynamic_module_callback_http_append_received_request_body is called by the module to append + * the given data to the end of the current request body. * * @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the * corresponding HTTP filter. @@ -1422,87 +1475,117 @@ bool envoy_dynamic_module_callback_http_get_request_body_vector_size( * @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( +bool envoy_dynamic_module_callback_http_append_received_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. + * envoy_dynamic_module_callback_http_append_buffered_request_body is called by the module to append + * the given data to the end of the buffered 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_buffered_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_received_request_body is called by the module to drain + * the given number of bytes from the current 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( +bool envoy_dynamic_module_callback_http_drain_received_request_body( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t number_of_bytes); /** - * envoy_dynamic_module_callback_http_inject_request_body is called by the module to - * inject the given request data directly into the filter stream. This method should only be called - * from a scheduled event. + * envoy_dynamic_module_callback_http_drain_buffered_request_body is called by the module to drain + * the given number of bytes from the buffered 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 data is the pointer to the buffer of the data to be injected. - * @param length is the length of the data. - * @param end_stream is true if this is the last data to be injected. + * @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_inject_request_body( +bool envoy_dynamic_module_callback_http_drain_buffered_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_received_request_body_vector, but for + * the current response body. See the comments on + * envoy_dynamic_module_callback_http_get_received_request_body_vector for more details. + */ +bool envoy_dynamic_module_callback_http_get_received_response_body_vector( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, - envoy_dynamic_module_type_buffer_module_ptr data, size_t length, bool end_stream); + envoy_dynamic_module_type_envoy_buffer* result_buffer_vector); /** - * 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. + * This is the same as envoy_dynamic_module_callback_http_get_buffered_request_body_vector, but for + * the buffered response body. See the comments on + * envoy_dynamic_module_callback_http_get_buffered_request_body_vector for more details. */ -bool envoy_dynamic_module_callback_http_get_response_body_vector( +bool envoy_dynamic_module_callback_http_get_buffered_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. + * This is the same as envoy_dynamic_module_callback_http_get_received_request_body_vector_size, but + * for the current response body. See the comments on + * envoy_dynamic_module_callback_http_get_received_request_body_vector_size for more details. */ -bool envoy_dynamic_module_callback_http_get_response_body_vector_size( +bool envoy_dynamic_module_callback_http_get_received_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. + * This is the same as envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size, but + * for the buffered response body. See the comments on + * envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size for more details. */ -bool envoy_dynamic_module_callback_http_append_response_body( +bool envoy_dynamic_module_callback_http_get_buffered_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_received_request_body, but for the + * current response body. See the comments on + * envoy_dynamic_module_callback_http_append_received_request_body for more details. + */ +bool envoy_dynamic_module_callback_http_append_received_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_append_buffered_request_body, but for the + * buffered response body. See the comments on + * envoy_dynamic_module_callback_http_append_buffered_request_body for more details. + */ +bool envoy_dynamic_module_callback_http_append_buffered_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. + * This is the same as envoy_dynamic_module_callback_http_drain_received_request_body, but for the + * current response body. See the comments on + * envoy_dynamic_module_callback_http_drain_received_request_body for more details. */ -bool envoy_dynamic_module_callback_http_drain_response_body( +bool envoy_dynamic_module_callback_http_drain_received_response_body( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t number_of_bytes); /** - * envoy_dynamic_module_callback_http_inject_response_body is called by the module to - * inject the given response data directly into the filter stream. This method should only be called - * from a scheduled event. - * - * @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 injected. - * @param length is the length of the data. - * @param end_stream is true if this is the last data to be injected. - * @return true if the body is available, false otherwise. + * This is the same as envoy_dynamic_module_callback_http_drain_buffered_request_body, but for the + * buffered response body. See the comments on + * envoy_dynamic_module_callback_http_drain_buffered_request_body for more details. */ -bool envoy_dynamic_module_callback_http_inject_response_body( - envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, - envoy_dynamic_module_type_buffer_module_ptr data, size_t length, bool end_stream); +bool envoy_dynamic_module_callback_http_drain_buffered_response_body( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t number_of_bytes); // ---------------------------- Metadata Callbacks ----------------------------- diff --git a/source/extensions/dynamic_modules/abi_version.h b/source/extensions/dynamic_modules/abi_version.h index ffe35c29294a6..f31a24fdff144 100644 --- a/source/extensions/dynamic_modules/abi_version.h +++ b/source/extensions/dynamic_modules/abi_version.h @@ -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 = "dcd3808436396000e681f7014dda187ca87708995367ea1374e4b3191281cb7c"; +const char* kAbiVersion = "52972436b8fd594556e76e1f9adfe1c22b7fd4a6bd3bbe6d9cdc8e0d7903dc68"; #ifdef __cplusplus } // namespace DynamicModules diff --git a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs index 478430d5b3073..c35e21b0d69ae 100644 --- a/source/extensions/dynamic_modules/sdk/rust/src/lib.rs +++ b/source/extensions/dynamic_modules/sdk/rust/src/lib.rs @@ -776,7 +776,10 @@ pub trait EnvoyHttpFilter { /// Returns true if the operation is successful. fn set_filter_state_bytes(&mut self, key: &[u8], value: &[u8]) -> bool; - /// Get the currently buffered request body. The body is represented as a list of [`EnvoyBuffer`]. + /// Get the received request body (the request body pieces received in the latest event). + /// This should only be used in the [`HttpFilter::on_request_body`] callback. + /// + /// 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 @@ -794,45 +797,78 @@ pub trait EnvoyHttpFilter { /// // Mutable static storage is used for the test to simulate the response body operation. /// static mut BUFFER: [u8; 10] = *b"helloworld"; /// envoy_filter - /// .expect_get_request_body() + /// .expect_get_received_request_body() /// .returning(|| Some(vec![EnvoyMutBuffer::new(unsafe { &mut BUFFER })])); - /// envoy_filter.expect_drain_request_body().return_const(true); + /// envoy_filter + /// .expect_drain_received_request_body() + /// .return_const(true); /// /// - /// // Calculate the size of the request body in bytes. - /// let buffers = envoy_filter.get_request_body().unwrap(); + /// // Calculate the size of the new received request body in bytes. + /// let buffers = envoy_filter.get_received_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)); + /// // drain the new received request body. + /// assert!(envoy_filter.drain_received_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>>; + fn get_received_request_body<'a>(&'a mut self) -> Option>>; - /// Drain the given number of bytes from the front of the request body. + /// Similar to [`get_received_request_body`], but returns the buffered request body + /// (the request body pieces buffered so far in the filter chain). + fn get_buffered_request_body<'a>(&'a mut self) -> Option>>; + + /// Drain the given number of bytes from the front of the received request body. + /// This should only be used in the [`HttpFilter::on_request_body`] callback. /// /// 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; + fn drain_received_request_body(&mut self, number_of_bytes: usize) -> bool; + + /// Similar to [`drain_received_request_body`], but drains from the buffered request body. + /// + /// This method should only be used by the final data-processing filter in the chain. + /// In other words, a filter may safely modify the buffered body only if no later filters + /// in the chain have accessed it yet. + /// + /// 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_buffered_request_body(&mut self, number_of_bytes: usize) -> bool; - /// Append the given data to the end of request body. + /// Append the given data to the end of the received request body. + /// This should only be used in the [`HttpFilter::on_request_body`] callback. /// /// 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; + fn append_received_request_body(&mut self, data: &[u8]) -> bool; + + /// Similar to [`append_received_request_body`], but appends to the buffered request body. + /// + /// This method should only be used by the final data-processing filter in the chain. + /// In other words, a filter may safely modify the buffered body only if no later filters + /// in the chain have accessed it yet. + /// + /// 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_buffered_request_body(&mut self, data: &[u8]) -> bool; - /// Get the currently buffered response body. The body is represented as a list of + /// Get the received response body (the response body pieces received in the latest event). + /// This should only be used in the [`HttpFilter::on_response_body`] callback. + /// + /// 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 @@ -850,43 +886,73 @@ pub trait EnvoyHttpFilter { /// // Mutable static storage is used for the test to simulate the response body operation. /// static mut BUFFER: [u8; 10] = *b"helloworld"; /// envoy_filter - /// .expect_get_response_body() + /// .expect_get_received_response_body() /// .returning(|| Some(vec![EnvoyMutBuffer::new(unsafe { &mut BUFFER })])); - /// envoy_filter.expect_drain_response_body().return_const(true); + /// envoy_filter + /// .expect_drain_received_response_body() + /// .return_const(true); /// /// - /// // Calculate the size of the response body in bytes. - /// let buffers = envoy_filter.get_response_body().unwrap(); + /// // Calculate the size of the received response body in bytes. + /// let buffers = envoy_filter.get_received_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)); + /// // drain the received response body. + /// assert!(envoy_filter.drain_received_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>>; + fn get_received_response_body<'a>(&'a mut self) -> Option>>; + + /// Similar to [`get_received_response_body`], but returns the buffered response body + /// (the response body pieces buffered so far in the filter chain). + fn get_buffered_response_body<'a>(&'a mut self) -> Option>>; - /// Drain the given number of bytes from the front of the response body. + /// Drain the given number of bytes from the front of the received response body. + /// This should only be used in the [`HttpFilter::on_response_body`] callback. /// /// 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; + fn drain_received_response_body(&mut self, number_of_bytes: usize) -> bool; + + /// Similar to [`drain_received_response_body`], but drains from the buffered response body. + /// + /// This method should only be used by the final data-processing filter in the chain. + /// In other words, a filter may safely modify the buffered body only if no later filters + /// in the chain have accessed it yet. + /// + /// 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_buffered_response_body(&mut self, number_of_bytes: usize) -> bool; - /// Append the given data to the end of the response body. + /// Append the given data to the end of the received response body. + /// This should only be used in the [`HttpFilter::on_response_body`] callback. /// /// 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; + fn append_received_response_body(&mut self, data: &[u8]) -> bool; + + /// Similar to [`append_received_response_body`], but appends to the buffered response body. + /// + /// This method should only be used by the final data-processing filter in the chain. + /// In other words, a filter may safely modify the buffered body only if no later filters + /// in the chain have accessed it yet. + /// + /// 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_buffered_response_body(&mut self, data: &[u8]) -> bool; /// Clear the route cache calculated during a previous phase of the filter chain. /// @@ -1436,10 +1502,39 @@ impl EnvoyHttpFilter for EnvoyHttpFilterImpl { } } - fn get_request_body(&mut self) -> Option> { + fn get_received_request_body(&mut self) -> Option> { + let mut size: usize = 0; + let ok = unsafe { + abi::envoy_dynamic_module_callback_http_get_received_request_body_vector_size( + self.raw_ptr, + &mut size, + ) + }; + if !ok || size == 0 { + return None; + } + + let buffers: Vec = vec![EnvoyMutBuffer::default(); size]; + let success = unsafe { + abi::envoy_dynamic_module_callback_http_get_received_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 get_buffered_request_body(&mut self) -> Option>> { let mut size: usize = 0; let ok = unsafe { - abi::envoy_dynamic_module_callback_http_get_request_body_vector_size(self.raw_ptr, &mut size) + abi::envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size( + self.raw_ptr, + &mut size, + ) }; if !ok || size == 0 { return None; @@ -1447,7 +1542,7 @@ impl EnvoyHttpFilter for EnvoyHttpFilterImpl { let buffers: Vec = vec![EnvoyMutBuffer::default(); size]; let success = unsafe { - abi::envoy_dynamic_module_callback_http_get_request_body_vector( + abi::envoy_dynamic_module_callback_http_get_buffered_request_body_vector( self.raw_ptr, buffers.as_ptr() as *mut abi::envoy_dynamic_module_type_envoy_buffer, ) @@ -1459,25 +1554,51 @@ impl EnvoyHttpFilter for EnvoyHttpFilterImpl { } } - fn drain_request_body(&mut self, number_of_bytes: usize) -> bool { + fn drain_received_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) + abi::envoy_dynamic_module_callback_http_drain_received_request_body( + self.raw_ptr, + number_of_bytes, + ) } } - fn append_request_body(&mut self, data: &[u8]) -> bool { + fn drain_buffered_request_body(&mut self, number_of_bytes: usize) -> bool { unsafe { - abi::envoy_dynamic_module_callback_http_append_request_body( + abi::envoy_dynamic_module_callback_http_drain_buffered_request_body( + self.raw_ptr, + number_of_bytes, + ) + } + } + + fn append_received_request_body(&mut self, data: &[u8]) -> bool { + unsafe { + abi::envoy_dynamic_module_callback_http_append_received_request_body( + self.raw_ptr, + data.as_ptr() as *const _ as *mut _, + data.len(), + ) + } + } + + fn append_buffered_request_body(&mut self, data: &[u8]) -> bool { + unsafe { + abi::envoy_dynamic_module_callback_http_append_buffered_request_body( self.raw_ptr, data.as_ptr() as *const _ as *mut _, data.len(), ) } } - fn get_response_body(&mut self) -> Option> { + + fn get_received_response_body(&mut self) -> Option> { let mut size: usize = 0; let ok = unsafe { - abi::envoy_dynamic_module_callback_http_get_response_body_vector_size(self.raw_ptr, &mut size) + abi::envoy_dynamic_module_callback_http_get_received_response_body_vector_size( + self.raw_ptr, + &mut size, + ) }; if !ok || size == 0 { return None; @@ -1485,7 +1606,7 @@ impl EnvoyHttpFilter for EnvoyHttpFilterImpl { let buffers: Vec = vec![EnvoyMutBuffer::default(); size]; let success = unsafe { - abi::envoy_dynamic_module_callback_http_get_response_body_vector( + abi::envoy_dynamic_module_callback_http_get_received_response_body_vector( self.raw_ptr, buffers.as_ptr() as *mut abi::envoy_dynamic_module_type_envoy_buffer, ) @@ -1497,15 +1618,63 @@ impl EnvoyHttpFilter for EnvoyHttpFilterImpl { } } - fn drain_response_body(&mut self, number_of_bytes: usize) -> bool { + fn get_buffered_response_body(&mut self) -> Option>> { + let mut size: usize = 0; + let ok = unsafe { + abi::envoy_dynamic_module_callback_http_get_buffered_response_body_vector_size( + self.raw_ptr, + &mut size, + ) + }; + if !ok || size == 0 { + return None; + } + + let buffers: Vec = vec![EnvoyMutBuffer::default(); size]; + let success = unsafe { + abi::envoy_dynamic_module_callback_http_get_buffered_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_received_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) + abi::envoy_dynamic_module_callback_http_drain_received_response_body( + self.raw_ptr, + number_of_bytes, + ) + } + } + + fn drain_buffered_response_body(&mut self, number_of_bytes: usize) -> bool { + unsafe { + abi::envoy_dynamic_module_callback_http_drain_buffered_response_body( + self.raw_ptr, + number_of_bytes, + ) + } + } + + fn append_received_response_body(&mut self, data: &[u8]) -> bool { + unsafe { + abi::envoy_dynamic_module_callback_http_append_received_response_body( + self.raw_ptr, + data.as_ptr() as *const _ as *mut _, + data.len(), + ) } } - fn append_response_body(&mut self, data: &[u8]) -> bool { + fn append_buffered_response_body(&mut self, data: &[u8]) -> bool { unsafe { - abi::envoy_dynamic_module_callback_http_append_response_body( + abi::envoy_dynamic_module_callback_http_append_buffered_response_body( self.raw_ptr, data.as_ptr() as *const _ as *mut _, data.len(), diff --git a/source/extensions/filters/http/dynamic_modules/abi_impl.cc b/source/extensions/filters/http/dynamic_modules/abi_impl.cc index b621f3270a354..2d2b166c273fb 100644 --- a/source/extensions/filters/http/dynamic_modules/abi_impl.cc +++ b/source/extensions/filters/http/dynamic_modules/abi_impl.cc @@ -9,6 +9,20 @@ namespace Envoy { namespace Extensions { namespace DynamicModules { namespace HttpFilters { +namespace { + +void bodyBufferToModule(const Buffer::Instance& buffer, + envoy_dynamic_module_type_envoy_buffer* result_buffer_vector) { + auto raw_slices = buffer.getRawSlices(std::nullopt); + auto counter = 0; + for (const auto& slice : raw_slices) { + result_buffer_vector[counter].length = slice.len_; + result_buffer_vector[counter].ptr = static_cast(slice.mem_); + counter++; + } +} + +} // namespace static Stats::StatNameTagVector buildTagsForModuleMetric(DynamicModuleHttpFilter& filter, const Stats::StatNameVec& label_names, @@ -884,74 +898,102 @@ bool envoy_dynamic_module_callback_http_get_filter_state_bytes( return true; } -bool envoy_dynamic_module_callback_http_get_request_body_vector( +bool envoy_dynamic_module_callback_http_get_received_request_body_vector( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_envoy_buffer* result_buffer_vector) { + auto filter = static_cast(filter_envoy_ptr); + auto buffer = filter->current_request_body_; + if (!buffer) { + return false; + } + // See the comment on current_request_body_ for when we reach this. + bodyBufferToModule(*buffer, result_buffer_vector); + return true; +} + +bool envoy_dynamic_module_callback_http_get_buffered_request_body_vector( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, envoy_dynamic_module_type_envoy_buffer* result_buffer_vector) { auto filter = static_cast(filter_envoy_ptr); auto buffer = filter->decoder_callbacks_->decodingBuffer(); if (!buffer) { - buffer = filter->current_request_body_; - if (!buffer) { - return false; - } - // See the comment on current_request_body_ for when we reach this. + return false; } - auto raw_slices = buffer->getRawSlices(std::nullopt); - auto counter = 0; - for (const auto& slice : raw_slices) { - result_buffer_vector[counter].length = slice.len_; - result_buffer_vector[counter].ptr = static_cast(slice.mem_); - counter++; + bodyBufferToModule(*buffer, result_buffer_vector); + return true; +} + +bool envoy_dynamic_module_callback_http_get_received_request_body_vector_size( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t* size) { + auto filter = static_cast(filter_envoy_ptr); + auto buffer = filter->current_request_body_; + if (!buffer) { + return false; } + // See the comment on current_request_body_ for when we reach this line. + *size = buffer->getRawSlices(std::nullopt).size(); return true; } -bool envoy_dynamic_module_callback_http_get_request_body_vector_size( +bool envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t* size) { auto filter = static_cast(filter_envoy_ptr); auto buffer = filter->decoder_callbacks_->decodingBuffer(); if (!buffer) { - buffer = filter->current_request_body_; - if (!buffer) { - return false; - } - // See the comment on current_request_body_ for when we reach this line. + return false; } *size = buffer->getRawSlices(std::nullopt).size(); return true; } -bool envoy_dynamic_module_callback_http_append_request_body( +bool envoy_dynamic_module_callback_http_append_received_request_body( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, envoy_dynamic_module_type_buffer_module_ptr data, size_t length) { auto filter = static_cast(filter_envoy_ptr); - if (!filter->decoder_callbacks_->decodingBuffer()) { - if (filter->current_request_body_) { // See the comment on current_request_body_ for when we - // enter this block. - filter->current_request_body_->add(absl::string_view(static_cast(data), length)); - return true; - } + auto buffer = filter->current_request_body_; + if (!buffer) { return false; } - filter->decoder_callbacks_->modifyDecodingBuffer([data, length](Buffer::Instance& buffer) { - buffer.add(absl::string_view(static_cast(data), length)); - }); + buffer->add(absl::string_view(static_cast(data), length)); + return true; +} + +bool envoy_dynamic_module_callback_http_append_buffered_request_body( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_buffer_module_ptr data, size_t length) { + auto filter = static_cast(filter_envoy_ptr); + auto buffer = filter->decoder_callbacks_->decodingBuffer(); + if (!buffer) { + Buffer::OwnedImpl buffer; + buffer.add(data, length); + filter->decoder_callbacks_->addDecodedData(buffer, true); + } else { + filter->decoder_callbacks_->modifyDecodingBuffer([data, length](Buffer::Instance& buffer) { + buffer.add(absl::string_view(static_cast(data), length)); + }); + } return true; } -bool envoy_dynamic_module_callback_http_drain_request_body( +bool envoy_dynamic_module_callback_http_drain_received_request_body( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t number_of_bytes) { auto filter = static_cast(filter_envoy_ptr); - if (!filter->decoder_callbacks_->decodingBuffer()) { - if (filter->current_request_body_) { // See the comment on current_request_body_ for when we - // enter this block. - auto size = std::min(filter->current_request_body_->length(), number_of_bytes); - filter->current_request_body_->drain(size); - return true; - } + auto buffer = filter->current_request_body_; + if (!buffer) { return false; } + auto size = std::min(filter->current_request_body_->length(), number_of_bytes); + filter->current_request_body_->drain(size); + return true; +} +bool envoy_dynamic_module_callback_http_drain_buffered_request_body( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t number_of_bytes) { + auto filter = static_cast(filter_envoy_ptr); + auto buffer = filter->decoder_callbacks_->decodingBuffer(); + if (!buffer) { + return false; + } filter->decoder_callbacks_->modifyDecodingBuffer([number_of_bytes](Buffer::Instance& buffer) { auto size = std::min(buffer.length(), number_of_bytes); buffer.drain(size); @@ -959,75 +1001,102 @@ bool envoy_dynamic_module_callback_http_drain_request_body( return true; } -bool envoy_dynamic_module_callback_http_get_response_body_vector( +bool envoy_dynamic_module_callback_http_get_received_response_body_vector( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_envoy_buffer* result_buffer_vector) { + auto filter = static_cast(filter_envoy_ptr); + auto buffer = filter->current_response_body_; + if (!buffer) { + return false; + } + // See the comment on current_response_body_ for when we reach this. + bodyBufferToModule(*buffer, result_buffer_vector); + return true; +} + +bool envoy_dynamic_module_callback_http_get_buffered_response_body_vector( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, envoy_dynamic_module_type_envoy_buffer* result_buffer_vector) { auto filter = static_cast(filter_envoy_ptr); auto buffer = filter->encoder_callbacks_->encodingBuffer(); if (!buffer) { - buffer = filter->current_response_body_; - if (!buffer) { - return false; - } - // See the comment on current_response_body_ for when we reach this line. + return false; } - auto raw_slices = buffer->getRawSlices(std::nullopt); - auto counter = 0; - for (const auto& slice : raw_slices) { - result_buffer_vector[counter].length = slice.len_; - result_buffer_vector[counter].ptr = static_cast(slice.mem_); - counter++; + bodyBufferToModule(*buffer, result_buffer_vector); + return true; +} + +bool envoy_dynamic_module_callback_http_get_received_response_body_vector_size( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t* size) { + auto filter = static_cast(filter_envoy_ptr); + auto buffer = filter->current_response_body_; + if (!buffer) { + return false; } + // See the comment on current_response_body_ for when we reach this line. + *size = buffer->getRawSlices(std::nullopt).size(); return true; } -bool envoy_dynamic_module_callback_http_get_response_body_vector_size( +bool envoy_dynamic_module_callback_http_get_buffered_response_body_vector_size( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t* size) { auto filter = static_cast(filter_envoy_ptr); auto buffer = filter->encoder_callbacks_->encodingBuffer(); if (!buffer) { - buffer = filter->current_response_body_; - if (!buffer) { - return false; - } - // See the comment on current_response_body_ for when we reach this line. + return false; } *size = buffer->getRawSlices(std::nullopt).size(); return true; } -bool envoy_dynamic_module_callback_http_append_response_body( +bool envoy_dynamic_module_callback_http_append_received_response_body( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, envoy_dynamic_module_type_buffer_module_ptr data, size_t length) { auto filter = static_cast(filter_envoy_ptr); - if (!filter->encoder_callbacks_->encodingBuffer()) { - if (filter->current_response_body_) { // See the comment on current_response_body_ for when we - // enter this block. - filter->current_response_body_->add( - absl::string_view(static_cast(data), length)); - return true; - } + auto buffer = filter->current_response_body_; + if (!buffer) { return false; } - filter->encoder_callbacks_->modifyEncodingBuffer([data, length](Buffer::Instance& buffer) { - buffer.add(absl::string_view(static_cast(data), length)); - }); + buffer->add(absl::string_view(static_cast(data), length)); + return true; +} + +bool envoy_dynamic_module_callback_http_append_buffered_response_body( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, + envoy_dynamic_module_type_buffer_module_ptr data, size_t length) { + auto filter = static_cast(filter_envoy_ptr); + auto buffer = filter->encoder_callbacks_->encodingBuffer(); + if (!buffer) { + Buffer::OwnedImpl buffer; + buffer.add(data, length); + filter->encoder_callbacks_->addEncodedData(buffer, true); + } else { + filter->encoder_callbacks_->modifyEncodingBuffer([data, length](Buffer::Instance& buffer) { + buffer.add(absl::string_view(static_cast(data), length)); + }); + } return true; } -bool envoy_dynamic_module_callback_http_drain_response_body( +bool envoy_dynamic_module_callback_http_drain_received_response_body( envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t number_of_bytes) { auto filter = static_cast(filter_envoy_ptr); - if (!filter->encoder_callbacks_->encodingBuffer()) { - if (filter->current_response_body_) { // See the comment on current_response_body_ for when we - // enter this block. - auto size = std::min(filter->current_response_body_->length(), number_of_bytes); - filter->current_response_body_->drain(size); - return true; - } + auto buffer = filter->current_response_body_; + if (!buffer) { return false; } + auto size = std::min(filter->current_response_body_->length(), number_of_bytes); + filter->current_response_body_->drain(size); + return true; +} +bool envoy_dynamic_module_callback_http_drain_buffered_response_body( + envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr, size_t number_of_bytes) { + auto filter = static_cast(filter_envoy_ptr); + auto buffer = filter->encoder_callbacks_->encodingBuffer(); + if (!buffer) { + return false; + } filter->encoder_callbacks_->modifyEncodingBuffer([number_of_bytes](Buffer::Instance& buffer) { auto size = std::min(buffer.length(), number_of_bytes); buffer.drain(size); diff --git a/test/extensions/dynamic_modules/http/abi_impl_test.cc b/test/extensions/dynamic_modules/http/abi_impl_test.cc index 1a79eab75c491..89ee935da99c6 100644 --- a/test/extensions/dynamic_modules/http/abi_impl_test.cc +++ b/test/extensions/dynamic_modules/http/abi_impl_test.cc @@ -611,35 +611,136 @@ TEST(ABIImpl, RequestBody) { size_t length = 0; // Non existing buffer should return false. + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_received_request_body_vector(&filter, nullptr)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_received_request_body_vector_size(&filter, &length)); + EXPECT_FALSE(envoy_dynamic_module_callback_http_drain_received_request_body(&filter, 0)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_append_received_request_body(&filter, nullptr, 0)); + + Buffer::OwnedImpl buffer; + filter.current_request_body_ = &buffer; + + // Empty buffer should return size 0 and drain should return work without problems. + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_received_request_body_vector_size(&filter, &length)); + EXPECT_EQ(length, 0); + EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_received_request_body(&filter, 0)); + + // Append data to the buffer. + const std::string data = "foo"; + envoy_dynamic_module_type_buffer_module_ptr data_ptr = const_cast(data.data()); + size_t data_length = data.size(); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_received_request_body(&filter, data_ptr, + data_length)); + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_received_request_body_vector_size(&filter, &length)); + EXPECT_EQ(buffer.toString(), data); + + // Get the data from the buffer. + auto result_buffer_vector = std::vector(length); + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_received_request_body_vector( + &filter, result_buffer_vector.data())); + EXPECT_EQ(bufferVectorToString(result_buffer_vector), data); + + // Add more data to the buffer. + const std::string data2 = "bar"; + const std::string data3 = "baz"; + envoy_dynamic_module_type_buffer_module_ptr data_ptr2 = const_cast(data2.data()); + size_t data_length2 = data2.size(); + envoy_dynamic_module_type_buffer_module_ptr data_ptr3 = const_cast(data3.data()); + size_t data_length3 = data3.size(); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_received_request_body(&filter, data_ptr2, + data_length2)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_received_request_body(&filter, data_ptr3, + data_length3)); + EXPECT_EQ(buffer.toString(), data + data2 + data3); + + // Check the data. + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_received_request_body_vector_size(&filter, &length)); + auto result_buffer_vector2 = std::vector(length); + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_received_request_body_vector( + &filter, result_buffer_vector2.data())); + EXPECT_EQ(bufferVectorToString(result_buffer_vector2), data + data2 + data3); + + // Drain the first 5 bytes. + EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_received_request_body(&filter, 5)); + + // Check the data. + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_received_request_body_vector_size(&filter, &length)); + auto result_buffer_vector3 = std::vector(length); + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_received_request_body_vector( + &filter, result_buffer_vector3.data())); + EXPECT_EQ(bufferVectorToString(result_buffer_vector3), "rbaz"); + + // Clear up the current_request_body_ pointer. + filter.current_request_body_ = nullptr; + + // Everything should return false again. + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_received_request_body_vector(&filter, nullptr)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_received_request_body_vector_size(&filter, &length)); + EXPECT_FALSE(envoy_dynamic_module_callback_http_drain_received_request_body(&filter, 0)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_append_received_request_body(&filter, nullptr, 0)); +} + +TEST(ABIImpl, BufferedRequestBody) { + Stats::SymbolTableImpl symbol_table; + DynamicModuleHttpFilter filter{nullptr, symbol_table}; + Http::MockStreamDecoderFilterCallbacks callbacks; + StreamInfo::MockStreamInfo stream_info; + EXPECT_CALL(callbacks, streamInfo()).WillRepeatedly(testing::ReturnRef(stream_info)); + filter.setDecoderFilterCallbacks(callbacks); + + EXPECT_CALL(callbacks, decodingBuffer()).WillRepeatedly(testing::Return(nullptr)); + EXPECT_CALL(callbacks, modifyDecodingBuffer(_)).Times(testing::AnyNumber()); + EXPECT_CALL(callbacks, addDecodedData(_, _)).Times(testing::AnyNumber()); + + size_t length = 0; + + // Non buffered buffer should return false. EXPECT_CALL(callbacks, decodingBuffer()).WillRepeatedly(testing::ReturnNull()); - EXPECT_FALSE(envoy_dynamic_module_callback_http_get_request_body_vector(&filter, nullptr)); - EXPECT_FALSE(envoy_dynamic_module_callback_http_get_request_body_vector_size(&filter, &length)); - EXPECT_FALSE(envoy_dynamic_module_callback_http_append_request_body(&filter, nullptr, 0)); - EXPECT_FALSE(envoy_dynamic_module_callback_http_drain_request_body(&filter, 0)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_buffered_request_body_vector(&filter, nullptr)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size(&filter, &length)); + EXPECT_FALSE(envoy_dynamic_module_callback_http_drain_buffered_request_body(&filter, 0)); + + // Append to buffered body always success. + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_buffered_request_body(&filter, nullptr, 0)); Buffer::OwnedImpl buffer; EXPECT_CALL(callbacks, decodingBuffer()).WillRepeatedly(testing::Return(&buffer)); EXPECT_CALL(callbacks, modifyDecodingBuffer(_)) .WillRepeatedly(Invoke( [&](std::function callback) -> void { callback(buffer); })); + EXPECT_CALL(callbacks, addDecodedData(_, true)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) -> void { buffer.add(data); })); // Empty buffer should return size 0 and drain should return work without problems. - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_request_body_vector_size(&filter, &length)); + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size(&filter, &length)); EXPECT_EQ(length, 0); - EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_request_body(&filter, 0)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_buffered_request_body(&filter, 0)); // Append data to the buffer. const std::string data = "foo"; envoy_dynamic_module_type_buffer_module_ptr data_ptr = const_cast(data.data()); size_t data_length = data.size(); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_buffered_request_body(&filter, data_ptr, + data_length)); EXPECT_TRUE( - envoy_dynamic_module_callback_http_append_request_body(&filter, data_ptr, data_length)); - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_request_body_vector_size(&filter, &length)); + envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size(&filter, &length)); EXPECT_EQ(buffer.toString(), data); // Get the data from the buffer. auto result_buffer_vector = std::vector(length); - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_request_body_vector( + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_buffered_request_body_vector( &filter, result_buffer_vector.data())); EXPECT_EQ(bufferVectorToString(result_buffer_vector), data); @@ -650,26 +751,28 @@ TEST(ABIImpl, RequestBody) { size_t data_length2 = data2.size(); envoy_dynamic_module_type_buffer_module_ptr data_ptr3 = const_cast(data3.data()); size_t data_length3 = data3.size(); - EXPECT_TRUE( - envoy_dynamic_module_callback_http_append_request_body(&filter, data_ptr2, data_length2)); - EXPECT_TRUE( - envoy_dynamic_module_callback_http_append_request_body(&filter, data_ptr3, data_length3)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_buffered_request_body(&filter, data_ptr2, + data_length2)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_buffered_request_body(&filter, data_ptr3, + data_length3)); EXPECT_EQ(buffer.toString(), data + data2 + data3); // Check the data. - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_request_body_vector_size(&filter, &length)); + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size(&filter, &length)); auto result_buffer_vector2 = std::vector(length); - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_request_body_vector( + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_buffered_request_body_vector( &filter, result_buffer_vector2.data())); EXPECT_EQ(bufferVectorToString(result_buffer_vector2), data + data2 + data3); // Drain the first 5 bytes. - EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_request_body(&filter, 5)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_buffered_request_body(&filter, 5)); // Check the data. - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_request_body_vector_size(&filter, &length)); + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_buffered_request_body_vector_size(&filter, &length)); auto result_buffer_vector3 = std::vector(length); - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_request_body_vector( + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_buffered_request_body_vector( &filter, result_buffer_vector3.data())); EXPECT_EQ(bufferVectorToString(result_buffer_vector3), "rbaz"); } @@ -685,45 +788,137 @@ TEST(ABIImpl, ResponseBody) { size_t length = 0; // Non existing buffer should return false. - EXPECT_CALL(callbacks, encodingBuffer()).WillRepeatedly(testing::ReturnNull()); - EXPECT_FALSE(envoy_dynamic_module_callback_http_get_response_body_vector(&filter, nullptr)); - EXPECT_FALSE(envoy_dynamic_module_callback_http_get_response_body_vector_size(&filter, &length)); - EXPECT_FALSE(envoy_dynamic_module_callback_http_append_response_body(&filter, nullptr, 0)); - EXPECT_FALSE(envoy_dynamic_module_callback_http_drain_response_body(&filter, 0)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_received_response_body_vector(&filter, nullptr)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_received_response_body_vector_size(&filter, &length)); + EXPECT_FALSE(envoy_dynamic_module_callback_http_drain_received_response_body(&filter, 0)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_append_received_response_body(&filter, nullptr, 0)); + + Buffer::OwnedImpl buffer; + filter.current_response_body_ = &buffer; - // Buffer is available via current_response_body_, not the stream encoder. + // Empty buffer should return size 0 and drain should return work without problems. + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_received_response_body_vector_size(&filter, &length)); + EXPECT_EQ(length, 0); + EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_received_response_body(&filter, 0)); + + // Append data to the buffer. const std::string data = "foo"; - Buffer::OwnedImpl current_buffer; - filter.current_response_body_ = ¤t_buffer; - EXPECT_TRUE(envoy_dynamic_module_callback_http_append_response_body( - &filter, const_cast(data.data()), 3)); - EXPECT_EQ(current_buffer.toString(), data); - EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_response_body(&filter, 3)); - EXPECT_EQ(current_buffer.toString(), ""); + envoy_dynamic_module_type_buffer_module_ptr data_ptr = const_cast(data.data()); + size_t data_length = data.size(); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_received_response_body(&filter, data_ptr, + data_length)); + EXPECT_EQ(buffer.toString(), data); + + // Get the data from the buffer. + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_received_response_body_vector_size(&filter, &length)); + auto result_buffer_vector = std::vector(length); + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_received_response_body_vector( + &filter, result_buffer_vector.data())); + EXPECT_EQ(bufferVectorToString(result_buffer_vector), data); + + // Add more data to the buffer. + const std::string data2 = "bar"; + const std::string data3 = "baz"; + envoy_dynamic_module_type_buffer_module_ptr data_ptr2 = const_cast(data2.data()); + size_t data_length2 = data2.size(); + envoy_dynamic_module_type_buffer_module_ptr data_ptr3 = const_cast(data3.data()); + size_t data_length3 = data3.size(); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_received_response_body(&filter, data_ptr2, + data_length2)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_received_response_body(&filter, data_ptr3, + data_length3)); + EXPECT_EQ(buffer.toString(), data + data2 + data3); + + // Check the data. + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_received_response_body_vector_size(&filter, &length)); + auto result_buffer_vector2 = std::vector(length); + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_received_response_body_vector( + &filter, result_buffer_vector2.data())); + EXPECT_EQ(bufferVectorToString(result_buffer_vector2), data + data2 + data3); + + // Drain the first 5 bytes. + EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_received_response_body(&filter, 5)); + + // Check the data. + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_received_response_body_vector_size(&filter, &length)); + auto result_buffer_vector3 = std::vector(length); + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_received_response_body_vector( + &filter, result_buffer_vector3.data())); + EXPECT_EQ(bufferVectorToString(result_buffer_vector3), "rbaz"); + + // Clear up the current_response_body_ pointer. filter.current_response_body_ = nullptr; + // Everything should return false again. + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_received_response_body_vector(&filter, nullptr)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_received_response_body_vector_size(&filter, &length)); + EXPECT_FALSE(envoy_dynamic_module_callback_http_drain_received_response_body(&filter, 0)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_append_received_response_body(&filter, nullptr, 0)); +} + +TEST(ABIImpl, BufferedResponseBody) { + Stats::SymbolTableImpl symbol_table; + DynamicModuleHttpFilter filter{nullptr, symbol_table}; + Http::MockStreamEncoderFilterCallbacks callbacks; + StreamInfo::MockStreamInfo stream_info; + EXPECT_CALL(callbacks, streamInfo()).WillRepeatedly(testing::ReturnRef(stream_info)); + filter.setEncoderFilterCallbacks(callbacks); + + EXPECT_CALL(callbacks, encodingBuffer()).WillRepeatedly(testing::Return(nullptr)); + EXPECT_CALL(callbacks, modifyEncodingBuffer(_)).Times(testing::AnyNumber()); + EXPECT_CALL(callbacks, addEncodedData(_, _)).Times(testing::AnyNumber()); + + size_t length = 0; + + // Non existing buffer should return false. + EXPECT_CALL(callbacks, encodingBuffer()).WillRepeatedly(testing::ReturnNull()); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_buffered_response_body_vector(&filter, nullptr)); + EXPECT_FALSE( + envoy_dynamic_module_callback_http_get_buffered_response_body_vector_size(&filter, &length)); + EXPECT_FALSE(envoy_dynamic_module_callback_http_drain_buffered_response_body(&filter, 0)); + + // Append to buffered body always success. + EXPECT_TRUE( + envoy_dynamic_module_callback_http_append_buffered_response_body(&filter, nullptr, 0)); + Buffer::OwnedImpl buffer; EXPECT_CALL(callbacks, encodingBuffer()).WillRepeatedly(testing::Return(&buffer)); EXPECT_CALL(callbacks, modifyEncodingBuffer(_)) .WillRepeatedly(Invoke( [&](std::function callback) -> void { callback(buffer); })); + EXPECT_CALL(callbacks, addEncodedData(_, true)) + .WillRepeatedly(Invoke([&](Buffer::Instance& data, bool) -> void { buffer.add(data); })); // Empty buffer should return size 0 and drain should return work without problems. - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_response_body_vector_size(&filter, &length)); + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_buffered_response_body_vector_size(&filter, &length)); EXPECT_EQ(length, 0); - EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_response_body(&filter, 0)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_buffered_response_body(&filter, 0)); // Append data to the buffer. + const std::string data = "foo"; envoy_dynamic_module_type_buffer_module_ptr data_ptr = const_cast(data.data()); size_t data_length = data.size(); - EXPECT_TRUE( - envoy_dynamic_module_callback_http_append_response_body(&filter, data_ptr, data_length)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_buffered_response_body(&filter, data_ptr, + data_length)); EXPECT_EQ(buffer.toString(), data); // Get the data from the buffer. - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_response_body_vector_size(&filter, &length)); + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_buffered_response_body_vector_size(&filter, &length)); auto result_buffer_vector = std::vector(length); - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_response_body_vector( + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_buffered_response_body_vector( &filter, result_buffer_vector.data())); EXPECT_EQ(bufferVectorToString(result_buffer_vector), data); @@ -734,26 +929,28 @@ TEST(ABIImpl, ResponseBody) { size_t data_length2 = data2.size(); envoy_dynamic_module_type_buffer_module_ptr data_ptr3 = const_cast(data3.data()); size_t data_length3 = data3.size(); - EXPECT_TRUE( - envoy_dynamic_module_callback_http_append_response_body(&filter, data_ptr2, data_length2)); - EXPECT_TRUE( - envoy_dynamic_module_callback_http_append_response_body(&filter, data_ptr3, data_length3)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_buffered_response_body(&filter, data_ptr2, + data_length2)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_append_buffered_response_body(&filter, data_ptr3, + data_length3)); EXPECT_EQ(buffer.toString(), data + data2 + data3); // Check the data. - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_response_body_vector_size(&filter, &length)); + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_buffered_response_body_vector_size(&filter, &length)); auto result_buffer_vector2 = std::vector(length); - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_response_body_vector( + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_buffered_response_body_vector( &filter, result_buffer_vector2.data())); EXPECT_EQ(bufferVectorToString(result_buffer_vector2), data + data2 + data3); // Drain the first 5 bytes. - EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_response_body(&filter, 5)); + EXPECT_TRUE(envoy_dynamic_module_callback_http_drain_buffered_response_body(&filter, 5)); // Check the data. - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_response_body_vector_size(&filter, &length)); + EXPECT_TRUE( + envoy_dynamic_module_callback_http_get_buffered_response_body_vector_size(&filter, &length)); auto result_buffer_vector3 = std::vector(length); - EXPECT_TRUE(envoy_dynamic_module_callback_http_get_response_body_vector( + EXPECT_TRUE(envoy_dynamic_module_callback_http_get_buffered_response_body_vector( &filter, result_buffer_vector3.data())); EXPECT_EQ(bufferVectorToString(result_buffer_vector3), "rbaz"); } diff --git a/test/extensions/dynamic_modules/test_data/rust/http.rs b/test/extensions/dynamic_modules/test_data/rust/http.rs index 6a1c87e2a8976..96eb9c4be4bed 100644 --- a/test/extensions/dynamic_modules/test_data/rust/http.rs +++ b/test/extensions/dynamic_modules/test_data/rust/http.rs @@ -695,16 +695,12 @@ struct BodyCallbacksFilter { response_body: Vec, } -impl Drop for BodyCallbacksFilter { - fn drop(&mut self) { - assert_eq!( - std::str::from_utf8(&self.request_body).unwrap(), - "nicenicenice" - ); - assert_eq!( - std::str::from_utf8(&self.response_body).unwrap(), - "coolcoolcool" - ); +impl BodyCallbacksFilter { + fn get_final_read_request_body<'a>(&'a self) -> &'a Vec { + &self.request_body + } + fn get_final_read_response_body<'a>(&'a self) -> &'a Vec { + &self.response_body } } @@ -764,34 +760,60 @@ impl std::io::Read for BodyReader<'_> { struct BodyWriter<'a, EHF: EnvoyHttpFilter> { envoy_filter: &'a mut EHF, request: bool, + received: bool, // true: new received body, false: old buffered body } impl<'a, EHF: EnvoyHttpFilter> BodyWriter<'a, EHF> { - fn new(envoy_filter: &'a mut EHF, request: bool) -> Self { + fn new(envoy_filter: &'a mut EHF, request: bool, received: bool) -> Self { // Before starting to write, drain the existing buffer content. - let current_vec = if request { - envoy_filter - .get_request_body() - .expect("request body is None") - } else { - envoy_filter - .get_response_body() - .expect("response body is None") - }; - - let buffer_bytes = current_vec - .iter() - .map(|buf| buf.as_slice().len()) - .sum::(); - - if request { - assert!(envoy_filter.drain_request_body(buffer_bytes)); + if received { + let optional_vec = if request { + envoy_filter.get_received_request_body() + } else { + envoy_filter.get_received_response_body() + }; + + if optional_vec.is_some() { + let received_vec = optional_vec.unwrap(); + + let buffer_bytes = received_vec + .iter() + .map(|buf| buf.as_slice().len()) + .sum::(); + + if request { + assert!(envoy_filter.drain_received_request_body(buffer_bytes)); + } else { + assert!(envoy_filter.drain_received_response_body(buffer_bytes)); + } + } } else { - assert!(envoy_filter.drain_response_body(buffer_bytes)); + let optional_vec = if request { + envoy_filter.get_buffered_request_body() + } else { + envoy_filter.get_buffered_response_body() + }; + + if optional_vec.is_some() { + let buffered_vec = optional_vec.unwrap(); + + let buffer_bytes = buffered_vec + .iter() + .map(|buf| buf.as_slice().len()) + .sum::(); + + if request { + assert!(envoy_filter.drain_buffered_request_body(buffer_bytes)); + } else { + assert!(envoy_filter.drain_buffered_response_body(buffer_bytes)); + } + } } + Self { envoy_filter, request, + received, } } } @@ -799,18 +821,36 @@ impl<'a, EHF: EnvoyHttpFilter> BodyWriter<'a, EHF> { impl<'a, EHF: EnvoyHttpFilter> std::io::Write for BodyWriter<'a, EHF> { fn write(&mut self, buf: &[u8]) -> std::io::Result { if self.request { - if !self.envoy_filter.append_request_body(buf) { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Buffer is not available", - )); + if self.received { + if !self.envoy_filter.append_received_request_body(buf) { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Buffer is not available", + )); + } + } else { + if !self.envoy_filter.append_buffered_request_body(buf) { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Buffer is not available", + )); + } } } else { - if !self.envoy_filter.append_response_body(buf) { - return Err(std::io::Error::new( - std::io::ErrorKind::Other, - "Buffer is not available", - )); + if self.received { + if !self.envoy_filter.append_received_response_body(buf) { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Buffer is not available", + )); + } + } else { + if !self.envoy_filter.append_buffered_response_body(buf) { + return Err(std::io::Error::new( + std::io::ErrorKind::Other, + "Buffer is not available", + )); + } } } @@ -828,23 +868,45 @@ impl HttpFilter for BodyCallbacksFilter { envoy_filter: &mut EHF, end_of_stream: bool, ) -> abi::envoy_dynamic_module_type_on_http_filter_request_body_status { - // Test reading request body. - let body = envoy_filter - .get_request_body() - .expect("request body is None"); - let mut reader = BodyReader::new(body); - let mut buf = vec![0; 1024]; - let n = std::io::Read::read(&mut reader, &mut buf).unwrap(); - self.request_body.extend_from_slice(&buf[.. n]); - // Drop the reader and try writing to the writer. - drop(reader); - - // Test writing to request body. - let mut writer = BodyWriter::new(envoy_filter, true); - std::io::Write::write(&mut writer, b"foo").unwrap(); - if end_of_stream { - std::io::Write::write(&mut writer, b"end").unwrap(); + { + // Test reading new received request body. + let body = envoy_filter.get_received_request_body(); + if body.is_some() { + let mut reader = BodyReader::new(body.unwrap()); + let mut buf = vec![0; 1024]; + let n = std::io::Read::read(&mut reader, &mut buf).unwrap(); + self.request_body.extend_from_slice(&buf[.. n]); + // Drop the reader and try writing to the writer. + drop(reader); + + // Test writing to request body. + let mut writer = BodyWriter::new(envoy_filter, true, true); + std::io::Write::write(&mut writer, b"foo").unwrap(); + if end_of_stream { + std::io::Write::write(&mut writer, b"end").unwrap(); + } + } + } + { + // Test reading old buffered request body. + let body = envoy_filter.get_buffered_request_body(); + if body.is_some() { + let mut reader = BodyReader::new(body.unwrap()); + let mut buf = vec![0; 1024]; + let n = std::io::Read::read(&mut reader, &mut buf).unwrap(); + self.request_body.extend_from_slice(&buf[.. n]); + // Drop the reader and try writing to the writer. + drop(reader); + + // Test writing to request body. + let mut writer = BodyWriter::new(envoy_filter, true, false); + std::io::Write::write(&mut writer, b"foo").unwrap(); + if end_of_stream { + std::io::Write::write(&mut writer, b"end").unwrap(); + } + } } + abi::envoy_dynamic_module_type_on_http_filter_request_body_status::Continue } @@ -853,23 +915,45 @@ impl HttpFilter for BodyCallbacksFilter { envoy_filter: &mut EHF, end_of_stream: bool, ) -> abi::envoy_dynamic_module_type_on_http_filter_response_body_status { - // Test reading response body. - let body = envoy_filter - .get_response_body() - .expect("response body is None"); - let mut reader = BodyReader::new(body); - let mut buffer = Vec::new(); - std::io::Read::read_to_end(&mut reader, &mut buffer).unwrap(); - self.response_body.extend_from_slice(&buffer); - // Drop the reader and try writing to the writer. - drop(reader); - - // Test writing to response body. - let mut writer = BodyWriter::new(envoy_filter, false); - std::io::Write::write(&mut writer, b"bar").unwrap(); - if end_of_stream { - std::io::Write::write(&mut writer, b"end").unwrap(); + { + // Test reading new received response body. + let body = envoy_filter.get_received_response_body(); + if body.is_some() { + let mut reader = BodyReader::new(body.unwrap()); + let mut buffer = Vec::new(); + std::io::Read::read_to_end(&mut reader, &mut buffer).unwrap(); + self.response_body.extend_from_slice(&buffer); + // Drop the reader and try writing to the writer. + drop(reader); + + // Test writing to response body. + let mut writer = BodyWriter::new(envoy_filter, false, true); + std::io::Write::write(&mut writer, b"bar").unwrap(); + if end_of_stream { + std::io::Write::write(&mut writer, b"end").unwrap(); + } + } + } + { + // Test reading old buffered response body. + let body = envoy_filter.get_buffered_response_body(); + if body.is_some() { + let mut reader = BodyReader::new(body.unwrap()); + let mut buffer = Vec::new(); + std::io::Read::read_to_end(&mut reader, &mut buffer).unwrap(); + self.response_body.extend_from_slice(&buffer); + // Drop the reader and try writing to the writer. + drop(reader); + + // Test writing to response body. + let mut writer = BodyWriter::new(envoy_filter, false, false); + std::io::Write::write(&mut writer, b"bar").unwrap(); + if end_of_stream { + std::io::Write::write(&mut writer, b"end").unwrap(); + } + } } + abi::envoy_dynamic_module_type_on_http_filter_response_body_status::Continue } } diff --git a/test/extensions/dynamic_modules/test_data/rust/http_integration_test.rs b/test/extensions/dynamic_modules/test_data/rust/http_integration_test.rs index d59aea9fc3e58..39e46b0d2e723 100644 --- a/test/extensions/dynamic_modules/test_data/rust/http_integration_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/http_integration_test.rs @@ -310,6 +310,14 @@ struct BodyCallbacksFilter { } impl HttpFilter for BodyCallbacksFilter { + fn on_request_headers( + &mut self, + _envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_request_headers_status { + envoy_dynamic_module_type_on_http_filter_request_headers_status::StopIteration + } + fn on_request_body( &mut self, envoy_filter: &mut EHF, @@ -322,25 +330,48 @@ impl HttpFilter for BodyCallbacksFilter { } self.seen_request_body = true; - let request_body = envoy_filter - .get_request_body() - .expect("request body not available"); - let mut body = String::new(); - for chunk in request_body { - body.push_str(std::str::from_utf8(chunk.as_slice()).unwrap()); + let mut received_body_len: usize = 0; + let mut buffered_body_len: usize = 0; + let mut body_content = String::new(); + + let buffered_body = envoy_filter.get_buffered_request_body(); + if buffered_body.is_some() { + for chunk in buffered_body.unwrap() { + buffered_body_len += chunk.as_slice().len(); + body_content.push_str(std::str::from_utf8(chunk.as_slice()).unwrap()); + } + } + + let received_body = envoy_filter.get_received_request_body(); + if received_body.is_some() { + for chunk in received_body.unwrap() { + received_body_len += chunk.as_slice().len(); + body_content.push_str(std::str::from_utf8(chunk.as_slice()).unwrap()); + } } - assert_eq!(body, "request_body"); + + assert_eq!(body_content, "request_body"); // Drain the request body. - envoy_filter.drain_request_body(body.len()); + envoy_filter.drain_received_request_body(received_body_len); + envoy_filter.drain_buffered_request_body(buffered_body_len); + // Append the new request body. - envoy_filter.append_request_body(b"new_request_body"); + envoy_filter.append_received_request_body(b"new_request_body"); // Plus we need to set the content length. envoy_filter.set_request_header("content-length", b"16"); envoy_dynamic_module_type_on_http_filter_request_body_status::Continue } + fn on_response_headers( + &mut self, + _envoy_filter: &mut EHF, + _end_of_stream: bool, + ) -> abi::envoy_dynamic_module_type_on_http_filter_response_headers_status { + envoy_dynamic_module_type_on_http_filter_response_headers_status::StopIteration + } + fn on_response_body( &mut self, envoy_filter: &mut EHF, @@ -352,19 +383,33 @@ impl HttpFilter for BodyCallbacksFilter { } self.seen_response_body = true; - let response_body = envoy_filter - .get_response_body() - .expect("response body not available"); - let mut body = String::new(); - for chunk in response_body { - body.push_str(std::str::from_utf8(chunk.as_slice()).unwrap()); + let mut buffered_body_len: usize = 0; + let mut received_body_len: usize = 0; + let mut body_content = String::new(); + + let buffered_body = envoy_filter.get_buffered_response_body(); + if buffered_body.is_some() { + for chunk in buffered_body.unwrap() { + buffered_body_len += chunk.as_slice().len(); + body_content.push_str(std::str::from_utf8(chunk.as_slice()).unwrap()); + } } - assert_eq!(body, "response_body"); + + let received_body = envoy_filter.get_received_response_body(); + if received_body.is_some() { + for chunk in received_body.unwrap() { + received_body_len += chunk.as_slice().len(); + body_content.push_str(std::str::from_utf8(chunk.as_slice()).unwrap()); + } + } + + assert_eq!(body_content, "response_body"); // Drain the response body. - envoy_filter.drain_response_body(body.len()); + envoy_filter.drain_received_response_body(received_body_len); + envoy_filter.drain_buffered_response_body(buffered_body_len); // Append the new response body. - envoy_filter.append_response_body(b"new_response_body"); + envoy_filter.append_received_response_body(b"new_response_body"); // Plus we need to set the content length. envoy_filter.set_response_header("content-length", b"17"); @@ -978,12 +1023,14 @@ impl HttpFilter for StreamingTerminalHttpFilter { EVENT_ID_READ_REQUEST => { if !self.request_closed { let mut body = Vec::new(); - if let Some(buffers) = envoy_filter.get_request_body() { + // The event is scheduled asynchronously and this will be called out of + // on_request_body. So, we get the buffered body here. + if let Some(buffers) = envoy_filter.get_buffered_request_body() { for buffer in buffers { body.extend_from_slice(buffer.as_slice()); } } - envoy_filter.drain_request_body(body.len()); + envoy_filter.drain_buffered_request_body(body.len()); self.send_large_response_chunk(envoy_filter); } else { envoy_filter.send_response_data(b"Thanks!", false); diff --git a/test/extensions/dynamic_modules/test_data/rust/http_test.rs b/test/extensions/dynamic_modules/test_data/rust/http_test.rs index 7f2fafe824d07..c535d4c412d89 100644 --- a/test/extensions/dynamic_modules/test_data/rust/http_test.rs +++ b/test/extensions/dynamic_modules/test_data/rust/http_test.rs @@ -117,7 +117,7 @@ fn test_body_callbacks_filter_on_bodies() { let mut envoy_filter = MockEnvoyHttpFilter::default(); envoy_filter - .expect_get_request_body() + .expect_get_received_request_body() .returning(|| { static mut BUF: [[u8; 4]; 3] = [*b"nice", *b"nice", *b"nice"]; Some(vec![ @@ -128,18 +128,44 @@ fn test_body_callbacks_filter_on_bodies() { }) .times(2); envoy_filter - .expect_drain_request_body() + .expect_get_buffered_request_body() + .returning(|| { + static mut BUF: [[u8; 4]; 3] = [*b"nice", *b"nice", *b"nice"]; + Some(vec![ + EnvoyMutBuffer::new(unsafe { &mut BUF[0] }), + EnvoyMutBuffer::new(unsafe { &mut BUF[1] }), + EnvoyMutBuffer::new(unsafe { &mut BUF[2] }), + ]) + }) + .times(2); + + envoy_filter + .expect_drain_received_request_body() + .return_const(true) + .once(); + envoy_filter + .expect_drain_buffered_request_body() .return_const(true) .once(); envoy_filter - .expect_append_request_body() + .expect_append_received_request_body() + .return_const(true) + .times(2); + envoy_filter + .expect_append_buffered_request_body() .return_const(true) .times(2); + f.on_request_body(&mut envoy_filter, true); + assert_eq!( + std::str::from_utf8(&f.get_final_read_request_body()).unwrap(), + "nicenicenicenicenicenice" + ); + envoy_filter - .expect_get_response_body() + .expect_get_received_response_body() .returning(|| { static mut BUF2: [[u8; 4]; 3] = [*b"cool", *b"cool", *b"cool"]; Some(vec![ @@ -150,13 +176,39 @@ fn test_body_callbacks_filter_on_bodies() { }) .times(2); envoy_filter - .expect_drain_response_body() + .expect_get_buffered_response_body() + .returning(|| { + static mut BUF2: [[u8; 4]; 3] = [*b"cool", *b"cool", *b"cool"]; + Some(vec![ + EnvoyMutBuffer::new(unsafe { &mut BUF2[0] }), + EnvoyMutBuffer::new(unsafe { &mut BUF2[1] }), + EnvoyMutBuffer::new(unsafe { &mut BUF2[2] }), + ]) + }) + .times(2); + + envoy_filter + .expect_drain_received_response_body() + .return_const(true) + .once(); + envoy_filter + .expect_drain_buffered_response_body() .return_const(true) .once(); envoy_filter - .expect_append_response_body() + .expect_append_received_response_body() + .return_const(true) + .times(2); + envoy_filter + .expect_append_buffered_response_body() .return_const(true) .times(2); + f.on_response_body(&mut envoy_filter, true); + + assert_eq!( + std::str::from_utf8(&f.get_final_read_response_body()).unwrap(), + "coolcoolcoolcoolcoolcool" + ); }