Skip to content

Commit 504874c

Browse files
authored
dynamic_module: add new streamable HTTP callout support (#42225)
## Description The Async streamable HTTP client is supported by Envoy since long time. Using it, we can create multiple different HTTP streams to different backends at the same time and distribute requests between these backends. This PR exposes this feature to dynamic modules. It could be used for developing MCP related dynamic modules. Fix #42045 --- **Commit Message:** dynamic_module: add new streamable HTTP callout support **Additional Description:** Add support for the Async streamable HTTP client in Dynamic Modules. **Risk Level:** Low **Testing:** Added Unit + Integration Tests **Docs Changes:** Added **Release Notes:** Added --------- Signed-off-by: Rohit Agrawal <[email protected]>
1 parent 454173a commit 504874c

File tree

17 files changed

+2360
-20
lines changed

17 files changed

+2360
-20
lines changed

changelogs/current.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,12 @@ new_features:
218218
change: |
219219
Enhanced dynamic module ABIs to support headers addition and body size retrieval.
220220
See the latest ABI header file for more details.
221+
- area: dynamic modules
222+
change: |
223+
Added support for streamable HTTP callouts in dynamic modules. Modules can now create
224+
streaming HTTP connections to upstream clusters using ``start_http_stream``, send request
225+
data and trailers incrementally, and receive streaming response headers, data, and trailers
226+
through dedicated callbacks.
221227
- area: udp_sink
222228
change: |
223229
Enhanced the UDP sink to support tapped messages larger than 64 KB.

source/extensions/dynamic_modules/abi.h

Lines changed: 228 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 array of buffers containing the body chunk.
811+
* @param data_count is the number of buffers.
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+
const envoy_dynamic_module_type_envoy_buffer* data, size_t data_count, 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,107 @@ 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 end_stream is true if the request stream should be ended after sending headers and body.
2139+
* If true and body_size > 0, the body will be sent with end_stream=true.
2140+
* If true and body_size is 0, headers will be sent with end_stream=true.
2141+
* If false, the module can send additional data or trailers using send_http_stream_data() or
2142+
* send_http_stream_trailers().
2143+
* @param timeout_milliseconds is the timeout for the stream in milliseconds. If 0, no timeout is
2144+
* set.
2145+
* @return envoy_dynamic_module_type_http_callout_init_result is the result of the stream
2146+
* initialization.
2147+
*/
2148+
envoy_dynamic_module_type_http_callout_init_result
2149+
envoy_dynamic_module_callback_http_filter_start_http_stream(
2150+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
2151+
envoy_dynamic_module_type_http_stream_envoy_ptr* stream_ptr_out,
2152+
envoy_dynamic_module_type_buffer_module_ptr cluster_name, size_t cluster_name_length,
2153+
envoy_dynamic_module_type_module_http_header* headers, size_t headers_size,
2154+
envoy_dynamic_module_type_buffer_module_ptr body, size_t body_size, bool end_stream,
2155+
uint64_t timeout_milliseconds);
2156+
2157+
/**
2158+
* envoy_dynamic_module_callback_http_filter_reset_http_stream is called by the module to reset
2159+
* or cancel an ongoing streamable HTTP callout. This causes the stream to be terminated and the
2160+
* envoy_dynamic_module_on_http_filter_http_stream_reset event hook to be called.
2161+
*
2162+
* This can be called at any point after the stream is started and before it completes. After
2163+
* calling this function, the stream handle becomes invalid.
2164+
*
2165+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
2166+
* corresponding HTTP filter.
2167+
* @param stream_ptr is the handle to the HTTP stream to reset.
2168+
*/
2169+
void envoy_dynamic_module_callback_http_filter_reset_http_stream(
2170+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
2171+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr);
2172+
2173+
/**
2174+
* envoy_dynamic_module_callback_http_stream_send_data is called by the module to send request
2175+
* body data on an active streamable HTTP callout. This can be called multiple times to stream
2176+
* the request body in chunks.
2177+
*
2178+
* This must be called after the stream is started and headers have been sent. It can be called
2179+
* multiple times until end_stream is set to true.
2180+
*
2181+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
2182+
* corresponding HTTP filter.
2183+
* @param stream_ptr is the handle to the HTTP stream.
2184+
* @param data is the pointer to the buffer of the data to send.
2185+
* @param data_length is the length of the data.
2186+
* @param end_stream is true if this is the last data (no trailers will follow).
2187+
* @return true if the operation is successful, false otherwise.
2188+
*/
2189+
bool envoy_dynamic_module_callback_http_stream_send_data(
2190+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
2191+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr,
2192+
envoy_dynamic_module_type_buffer_module_ptr data, size_t data_length, bool end_stream);
2193+
2194+
/**
2195+
* envoy_dynamic_module_callback_http_stream_send_trailers is called by the module to send
2196+
* request trailers on an active streamable HTTP callout. This implicitly ends the stream.
2197+
*
2198+
* This must be called after the stream is started and all request data has been sent.
2199+
* After calling this, no more data can be sent on the stream.
2200+
*
2201+
* @param filter_envoy_ptr is the pointer to the DynamicModuleHttpFilter object of the
2202+
* corresponding HTTP filter.
2203+
* @param stream_ptr is the handle to the HTTP stream.
2204+
* @param trailers is the trailers to send.
2205+
* @param trailers_size is the size of the trailers.
2206+
* @return true if the operation is successful, false otherwise.
2207+
*/
2208+
bool envoy_dynamic_module_callback_http_stream_send_trailers(
2209+
envoy_dynamic_module_type_http_filter_envoy_ptr filter_envoy_ptr,
2210+
envoy_dynamic_module_type_http_stream_envoy_ptr stream_ptr,
2211+
envoy_dynamic_module_type_module_http_header* trailers, size_t trailers_size);
2212+
19852213
/**
19862214
* envoy_dynamic_module_callback_http_filter_continue_decoding is called by the module to continue
19872215
* decoding the HTTP request.

source/extensions/dynamic_modules/abi_version.h

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

1111
#ifdef __cplusplus
1212
} // namespace DynamicModules

0 commit comments

Comments
 (0)