Skip to content

Commit f9ea7e9

Browse files
committed
dynamic_module: add new streamable HTTP callout support
Signed-off-by: Rohit Agrawal <[email protected]>
1 parent 44a57af commit f9ea7e9

File tree

18 files changed

+2285
-10
lines changed

18 files changed

+2285
-10
lines changed

changelogs/current.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ new_features:
196196
change: |
197197
Enhanced dynamic module ABIs to support headers addition and body size retrieval.
198198
See the latest ABI header file for more details.
199+
- area: dynamic modules
200+
change: |
201+
Added support for streamable HTTP callouts in dynamic modules. Modules can now create
202+
streaming HTTP connections to upstream clusters using ``start_http_stream``, send request
203+
data and trailers incrementally, and receive streaming response headers, data, and trailers
204+
through dedicated callbacks.
199205
- area: udp_sink
200206
change: |
201207
Enhanced the UDP sink to support tapped messages larger than 64 KB.

source/extensions/dynamic_modules/abi.h

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,6 +486,31 @@ typedef enum envoy_dynamic_module_type_http_callout_result {
486486
envoy_dynamic_module_type_http_callout_result_ExceedResponseBufferLimit,
487487
} envoy_dynamic_module_type_http_callout_result;
488488

489+
/**
490+
* envoy_dynamic_module_type_http_stream_envoy_ptr is a handle to an HTTP stream for streamable
491+
* callouts. This represents an ongoing HTTP stream initiated via
492+
* envoy_dynamic_module_callback_http_filter_start_http_stream.
493+
*
494+
* OWNERSHIP: Envoy owns the stream. The module must not store this pointer beyond the lifetime of
495+
* the stream callbacks.
496+
*/
497+
typedef void* envoy_dynamic_module_type_http_stream_envoy_ptr;
498+
499+
/**
500+
* envoy_dynamic_module_type_http_stream_reset_reason represents the reason for a stream reset.
501+
* This corresponds to `AsyncClient::StreamResetReason::*` in envoy/http/async_client.h.
502+
*/
503+
typedef enum envoy_dynamic_module_type_http_stream_reset_reason {
504+
envoy_dynamic_module_type_http_stream_reset_reason_ConnectionFailure,
505+
envoy_dynamic_module_type_http_stream_reset_reason_ConnectionTermination,
506+
envoy_dynamic_module_type_http_stream_reset_reason_LocalReset,
507+
envoy_dynamic_module_type_http_stream_reset_reason_LocalRefusedStreamReset,
508+
envoy_dynamic_module_type_http_stream_reset_reason_Overflow,
509+
envoy_dynamic_module_type_http_stream_reset_reason_RemoteReset,
510+
envoy_dynamic_module_type_http_stream_reset_reason_RemoteRefusedStreamReset,
511+
envoy_dynamic_module_type_http_stream_reset_reason_ProtocolError,
512+
} envoy_dynamic_module_type_http_stream_reset_reason;
513+
489514
/**
490515
* envoy_dynamic_module_type_metrics_result represents the result of the metrics operation.
491516
* Success means the operation was successful.
@@ -750,6 +775,108 @@ void envoy_dynamic_module_on_http_filter_http_callout_done(
750775
envoy_dynamic_module_type_envoy_http_header* headers, size_t headers_size,
751776
envoy_dynamic_module_type_envoy_buffer* body_chunks, size_t body_chunks_size);
752777

778+
/**
779+
* envoy_dynamic_module_on_http_filter_http_stream_headers is called when response headers are
780+
* received for a streamable HTTP callout stream.
781+
*
782+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
783+
* corresponding HTTP filter.
784+
* @param filter_module_ptr is the pointer to the in-module HTTP filter created by
785+
* envoy_dynamic_module_on_http_filter_new.
786+
* @param stream_ptr is the handle to the HTTP stream.
787+
* @param headers is the headers of the response.
788+
* @param headers_size is the size of the headers.
789+
* @param end_stream is true if this is the last data in the stream (no body or trailers will
790+
* follow).
791+
*
792+
* headers are owned by Envoy and are guaranteed to be valid until the end of this event hook.
793+
*/
794+
void envoy_dynamic_module_on_http_filter_http_stream_headers(
795+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
796+
envoy_dynamic_module_type_http_filter_module_ptr filter_module_ptr,
797+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr,
798+
envoy_dynamic_module_type_envoy_http_header* headers, size_t headers_size, bool end_stream);
799+
800+
/**
801+
* envoy_dynamic_module_on_http_filter_http_stream_data is called when a chunk of response body is
802+
* received for a streamable HTTP callout stream. This may be called multiple times for a single
803+
* stream as body chunks arrive.
804+
*
805+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
806+
* corresponding HTTP filter.
807+
* @param filter_module_ptr is the pointer to the in-module HTTP filter created by
808+
* envoy_dynamic_module_on_http_filter_new.
809+
* @param stream_ptr is the handle to the HTTP stream.
810+
* @param data is the pointer to the buffer containing the body chunk.
811+
* @param data_length is the length of the data.
812+
* @param end_stream is true if this is the last data in the stream (no trailers will follow).
813+
*
814+
* data is owned by Envoy and is guaranteed to be valid until the end of this event hook.
815+
*/
816+
void envoy_dynamic_module_on_http_filter_http_stream_data(
817+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
818+
envoy_dynamic_module_type_http_filter_module_ptr filter_module_ptr,
819+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr,
820+
envoy_dynamic_module_type_buffer_envoy_ptr data, size_t data_length, bool end_stream);
821+
822+
/**
823+
* envoy_dynamic_module_on_http_filter_http_stream_trailers is called when response trailers are
824+
* received for a streamable HTTP callout stream. This is called after headers and any data chunks.
825+
*
826+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
827+
* corresponding HTTP filter.
828+
* @param filter_module_ptr is the pointer to the in-module HTTP filter created by
829+
* envoy_dynamic_module_on_http_filter_new.
830+
* @param stream_ptr is the handle to the HTTP stream.
831+
* @param trailers is the trailers of the response.
832+
* @param trailers_size is the size of the trailers.
833+
*
834+
* trailers are owned by Envoy and are guaranteed to be valid until the end of this event hook.
835+
*/
836+
void envoy_dynamic_module_on_http_filter_http_stream_trailers(
837+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
838+
envoy_dynamic_module_type_http_filter_module_ptr filter_module_ptr,
839+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr,
840+
envoy_dynamic_module_type_envoy_http_header* trailers, size_t trailers_size);
841+
842+
/**
843+
* envoy_dynamic_module_on_http_filter_http_stream_complete is called when a streamable HTTP
844+
* callout stream completes successfully. This is called after all headers, data, and trailers have
845+
* been received.
846+
*
847+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
848+
* corresponding HTTP filter.
849+
* @param filter_module_ptr is the pointer to the in-module HTTP filter created by
850+
* envoy_dynamic_module_on_http_filter_new.
851+
* @param stream_ptr is the handle to the HTTP stream.
852+
*
853+
* After this callback, the stream is automatically cleaned up and stream_ptr becomes invalid.
854+
*/
855+
void envoy_dynamic_module_on_http_filter_http_stream_complete(
856+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
857+
envoy_dynamic_module_type_http_filter_module_ptr filter_module_ptr,
858+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr);
859+
860+
/**
861+
* envoy_dynamic_module_on_http_filter_http_stream_reset is called when a streamable HTTP callout
862+
* stream is reset or fails. This may be called instead of the complete callback if the stream
863+
* encounters an error.
864+
*
865+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
866+
* corresponding HTTP filter.
867+
* @param filter_module_ptr is the pointer to the in-module HTTP filter created by
868+
* envoy_dynamic_module_on_http_filter_new.
869+
* @param stream_ptr is the handle to the HTTP stream.
870+
* @param reason is the reason for the stream reset.
871+
*
872+
* After this callback, the stream is automatically cleaned up and stream_ptr becomes invalid.
873+
*/
874+
void envoy_dynamic_module_on_http_filter_http_stream_reset(
875+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
876+
envoy_dynamic_module_type_http_filter_module_ptr filter_module_ptr,
877+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr,
878+
envoy_dynamic_module_type_http_stream_reset_reason reason);
879+
753880
/**
754881
* envoy_dynamic_module_on_http_filter_scheduled is called when the HTTP filter is scheduled
755882
* to be executed on the worker thread where the HTTP filter is running with
@@ -1982,6 +2109,102 @@ envoy_dynamic_module_callback_http_filter_http_callout(
19822109
envoy_dynamic_module_type_buffer_module_ptr body, size_t body_size,
19832110
uint64_t timeout_milliseconds);
19842111

2112+
/**
2113+
* envoy_dynamic_module_callback_http_filter_start_http_stream is called by the module to start
2114+
* a streamable HTTP callout to a specified cluster. Unlike the one-shot HTTP callout, this allows
2115+
* the module to receive response headers, body chunks, and trailers through separate event hooks,
2116+
* enabling true streaming behavior.
2117+
*
2118+
* The stream will trigger the following event hooks in order:
2119+
* 1. envoy_dynamic_module_on_http_filter_http_stream_headers - when response headers arrive
2120+
* 2. envoy_dynamic_module_on_http_filter_http_stream_data - for each body chunk (may be called
2121+
* multiple times or not at all)
2122+
* 3. envoy_dynamic_module_on_http_filter_http_stream_trailers - when trailers arrive (optional)
2123+
* 4. envoy_dynamic_module_on_http_filter_http_stream_complete - when stream completes successfully
2124+
* OR
2125+
* envoy_dynamic_module_on_http_filter_http_stream_reset - if stream fails
2126+
*
2127+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
2128+
* corresponding HTTP filter.
2129+
* @param stream_ptr_out is a pointer to a variable where the stream handle will be stored. The
2130+
* module can use this handle to reset the stream via
2131+
* envoy_dynamic_module_callback_http_filter_reset_http_stream.
2132+
* @param cluster_name is the name of the cluster to which the stream is sent.
2133+
* @param cluster_name_length is the length of the cluster name.
2134+
* @param headers is the headers of the request. It must contain :method, :path and host headers.
2135+
* @param headers_size is the size of the headers.
2136+
* @param body is the pointer to the buffer of the body of the request.
2137+
* @param body_size is the length of the body.
2138+
* @param timeout_milliseconds is the timeout for the stream in milliseconds. If 0, no timeout is
2139+
* set.
2140+
* @return envoy_dynamic_module_type_http_callout_init_result is the result of the stream
2141+
* initialization.
2142+
*/
2143+
envoy_dynamic_module_type_http_callout_init_result
2144+
envoy_dynamic_module_callback_http_filter_start_http_stream(
2145+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
2146+
envoy_dynamic_module_type_http_stream_envoy_ptr* stream_ptr_out,
2147+
envoy_dynamic_module_type_buffer_module_ptr cluster_name, size_t cluster_name_length,
2148+
envoy_dynamic_module_type_module_http_header* headers, size_t headers_size,
2149+
envoy_dynamic_module_type_buffer_module_ptr body, size_t body_size,
2150+
uint64_t timeout_milliseconds);
2151+
2152+
/**
2153+
* envoy_dynamic_module_callback_http_filter_reset_http_stream is called by the module to reset
2154+
* or cancel an ongoing streamable HTTP callout. This causes the stream to be terminated and the
2155+
* envoy_dynamic_module_on_http_filter_http_stream_reset event hook to be called.
2156+
*
2157+
* This can be called at any point after the stream is started and before it completes. After
2158+
* calling this function, the stream handle becomes invalid.
2159+
*
2160+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
2161+
* corresponding HTTP filter.
2162+
* @param stream_ptr is the handle to the HTTP stream to reset.
2163+
*/
2164+
void envoy_dynamic_module_callback_http_filter_reset_http_stream(
2165+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
2166+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr);
2167+
2168+
/**
2169+
* envoy_dynamic_module_callback_http_stream_send_data is called by the module to send request
2170+
* body data on an active streamable HTTP callout. This can be called multiple times to stream
2171+
* the request body in chunks.
2172+
*
2173+
* This must be called after the stream is started and headers have been sent. It can be called
2174+
* multiple times until end_stream is set to true.
2175+
*
2176+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
2177+
* corresponding HTTP filter.
2178+
* @param stream_ptr is the handle to the HTTP stream.
2179+
* @param data is the pointer to the buffer of the data to send.
2180+
* @param data_length is the length of the data.
2181+
* @param end_stream is true if this is the last data (no trailers will follow).
2182+
* @return true if the operation is successful, false otherwise.
2183+
*/
2184+
bool envoy_dynamic_module_callback_http_stream_send_data(
2185+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
2186+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr,
2187+
envoy_dynamic_module_type_buffer_module_ptr data, size_t data_length, bool end_stream);
2188+
2189+
/**
2190+
* envoy_dynamic_module_callback_http_stream_send_trailers is called by the module to send
2191+
* request trailers on an active streamable HTTP callout. This implicitly ends the stream.
2192+
*
2193+
* This must be called after the stream is started and all request data has been sent.
2194+
* After calling this, no more data can be sent on the stream.
2195+
*
2196+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
2197+
* corresponding HTTP filter.
2198+
* @param stream_ptr is the handle to the HTTP stream.
2199+
* @param trailers is the trailers to send.
2200+
* @param trailers_size is the size of the trailers.
2201+
* @return true if the operation is successful, false otherwise.
2202+
*/
2203+
bool envoy_dynamic_module_callback_http_stream_send_trailers(
2204+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
2205+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr,
2206+
envoy_dynamic_module_type_module_http_header* trailers, size_t trailers_size);
2207+
19852208
/**
19862209
* envoy_dynamic_module_callback_http_filter_continue_decoding is called by the module to continue
19872210
* decoding the HTTP request.

source/extensions/dynamic_modules/abi_version.h

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
#pragma once
2+
23
#ifdef __cplusplus
3-
namespace Envoy {
4-
namespace Extensions {
5-
namespace DynamicModules {
4+
extern "C" {
65
#endif
7-
// This is the ABI version calculated as a sha256 hash of the ABI header files. When the ABI
8-
// changes, this value must change, and the correctness of this value is checked by the test.
9-
const char* kAbiVersion = "e7c1b3b48b6ef759ad0766916b5124ea01ca7117db79bc5b13d8cdd294deb9fc";
6+
7+
// ABI version string. This is the SHA256 hash of the abi.h file.
8+
static const char kAbiVersion[] =
9+
"1b6d11dabd2bfce494e17a2e482c5febf9c6d5c859ca26863740665af221d2c4";
1010

1111
#ifdef __cplusplus
12+
} // extern "C"
13+
14+
namespace Envoy {
15+
namespace Extensions {
16+
namespace DynamicModules {
17+
namespace AbiVersion {
18+
// For C++ code, also provide the version in the namespace.
19+
constexpr const char kAbiVersion[] =
20+
"1b6d11dabd2bfce494e17a2e482c5febf9c6d5c859ca26863740665af221d2c4";
21+
} // namespace AbiVersion
1222
} // namespace DynamicModules
1323
} // namespace Extensions
1424
} // namespace Envoy

source/extensions/dynamic_modules/dynamic_modules.cc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ newDynamicModule(const std::filesystem::path& object_file_absolute_path, const b
6565
absl::StrCat("Failed to initialize dynamic module: ", object_file_absolute_path.c_str()));
6666
}
6767
// Checks the kAbiVersion and the version of the dynamic module.
68-
if (absl::string_view(abi_version) != absl::string_view(kAbiVersion)) {
69-
return absl::InvalidArgumentError(
70-
absl::StrCat("ABI version mismatch: got ", abi_version, ", but expected ", kAbiVersion));
68+
if (absl::string_view(abi_version) != absl::string_view(AbiVersion::kAbiVersion)) {
69+
return absl::InvalidArgumentError(absl::StrCat("ABI version mismatch: got ", abi_version,
70+
", but expected ", AbiVersion::kAbiVersion));
7171
}
7272
return dynamic_module;
7373
}

source/extensions/dynamic_modules/sdk/rust/build.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ fn main() {
1818
println!("cargo:rerun-if-changed=abi.h");
1919
let bindings = bindgen::Builder::default()
2020
.header("../../abi.h")
21-
.header("../../abi_version.h")
2221
.clang_arg("-v")
2322
.default_enum_style(bindgen::EnumVariation::Rust {
2423
non_exhaustive: false,

0 commit comments

Comments
 (0)