-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
wip: platform extension filter poc #883
Conversation
@junr03 format checks pass for me locally - any idea how I can debug what's going on? |
Looks like buildifier failed based on the CI log. Do you have a diff when you do:
|
It might be the case that the version of buildifier you have is different than that in the build container https://github.com/envoyproxy/envoy-build-tools/blob/master/build_container/build_container_common.sh#L31. I would double check that. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is so cool! I think this will be pretty awesome. Some comments to get started.
|
||
envoy_package() | ||
|
||
envoy_cc_library( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eventually I believe we want this to be envoy_cc_extension
.
ENVOY_FILTER_HEADERS_STATUS_STOP_ITERATION, | ||
ENVOY_FILTER_HEADERS_STATUS_CONTINUE_AND_END_STREAM, | ||
ENVOY_FILTER_HEADERS_STATUS_STOP_ALL_ITERATION_AND_BUFFER, | ||
} envoy_filter_headers_status_t; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we discussed having a subset of these enums. Do you want to do that here in the declarations of in the mapping functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I decided to expose them all at this layer because there's no real reason not to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if we don't want to expose all of that functionality to platform filters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's fine to expose them here. To explain myself better, my impulse is to not make assumptions at this layer.
envoy_filter_on_headers_f on_response_headers; | ||
envoy_filter_on_data_f on_response_data; | ||
void* context; | ||
} envoy_http_filter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to create a distinction between a request(decode) and response(encode) filter? And a sub-type that is able to do both? What would be a good pattern to do that in c?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I mean you could use structs that have identical layouts but reserve the space for the missing callbacks. Or you could just use this one struct and set callbacks you're not using to null. I think the latter simplifies things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But then you would have to do null checks or assertions, vs. having the type dictate what functions you should implement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My plan was to default all callbacks to null and handle that gracefully in the base (bridge) implementation. I think it's more defensive, given the type variation.
/** | ||
* Harness to bridge Envoy filter invocations up to the platform layer. | ||
*/ | ||
class PlatformExtensionFilter final : public PassThroughFilter { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: above in c_types this was called PlatformHarnessFilter
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I renamed a few times and missed that one. Settled on PlatformExtensionFilter, as in "the filter that enables platform extensions".
PlatformExtensionFilter(envoy_http_filter platform_filter) : platform_filter_(platform_filter) {} | ||
|
||
// StreamDecoderFilter | ||
FilterHeadersStatus decodeHeaders(RequestHeaderMap& headers, bool end_stream) override; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now we don't need any of the other overrides except this one. We want to attempt to harness a call to decode headers to modify the request header, right? The PassThroughFilter
takes care of the rest.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't need them for a prototype, but I left them here for reference (and if we go this route, we'll definitely have them).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, but given that the pass through filter implements, I was wondering if we should delete to make this PR easier to read TIOLI
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either way.
|
||
FilterHeadersStatus PlatformExtensionFilter::decodeHeaders(RequestHeaderMap& headers, | ||
bool end_stream) { | ||
return mapStatus(platform_filter_.on_request_headers(Utility::toBridgeHeaders(headers), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Utility::toBridgeHeaders
copies the headers. If the platform modifies the values then it would not be reflected in the RequestHeaderMap& headers
. We can solve this a few ways (as of course you know). I might bias toward on_request_headers returning the value? It would cause a second copy but allows us to keep the memory contract were the caller of the function owns memory cleanup, i.e the platform code would own cleaning the passed in value, and this code would on clean up of the return value after we update RequestHeaderMap& headers
here.
wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
discussed offline, will add more context to the PR/comments
d0d1416
to
3ed68fa
Compare
@goaway looks like there is currently an assert being triggered: |
|
||
/** | ||
* Register an external runtime API for usage (most likely in extensions). | ||
* NOTE: This is a proof of concept implementation and a HACK. Registration is NOT thread-safe. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we intend to fix this before merging? Seems like both of these should be thread-safe now that we've proven it works
- name: envoy.filters.http.platform_extension | ||
typed_config: | ||
"@type": type.googleapis.com/envoymobile.extensions.filters.http.platform_extension.Bridging | ||
name: PlatformExample |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It'd be great to document how this works since it'll be removed before merging. Can we open an issue to document it?
case ENVOY_FILTER_HEADERS_STATUS_STOP_ALL_ITERATION_AND_BUFFER: | ||
return Http::FilterHeadersStatus::StopAllIterationAndBuffer; | ||
default: | ||
ASSERT(false, "unrecognized filter status from platform: {}"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like this is missing the format argument?
|
||
Http::FilterHeadersStatus BridgingFilter::decodeHeaders(Http::RequestHeaderMap& headers, | ||
bool end_stream) { | ||
// This is a hack that results in a full copy of the header map every time. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: this isn't really so much of a hack as an area for optimization, right? Can we file an issue and label it as tech debt?
} | ||
|
||
Http::FilterHeadersStatus | ||
BridgingFilter::encode100ContinueHeaders(Http::ResponseHeaderMap& /*headers*/) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you implement the rest of these functions, I assume we won't expose this one or metadata
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably not initially, though probably both ultimately.
func onRequestHeaders(_ headers: RequestHeaders, endStream: Bool) | ||
-> FilterHeaderStatus<RequestHeaders> | ||
-> UInt32 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this the intended end state for this interface? I assume not since it doesn't actually allow the filter to mutate the headers (the existing interface requires the filter to return a status containing headers, which also has the benefit of ensuring the filter cannot mutate the headers after the function returns)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed in other PR.
@@ -122,6 +133,7 @@ typedef NSDictionary<NSString *, NSArray<NSString *> *> EnvoyHeaders; | |||
@property (nonatomic, assign) UInt32 dnsRefreshSeconds; | |||
@property (nonatomic, assign) UInt32 dnsFailureRefreshSecondsBase; | |||
@property (nonatomic, assign) UInt32 dnsFailureRefreshSecondsMax; | |||
@property (nonatomic, strong) NSArray *httpFilters; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should these be NSArray<EnvoyHTTPFilter *> *
types?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure 👍
static EnvoyMutableHeaders *to_ios_mutable_headers(envoy_headers headers) { | ||
NSMutableDictionary *headerDict = [NSMutableDictionary new]; | ||
for (envoy_header_size_t i = 0; i < headers.length; i++) { | ||
envoy_header header = headers.headers[i]; | ||
NSString *headerKey = [[NSString alloc] initWithBytes:header.key.bytes | ||
length:header.key.length | ||
encoding:NSUTF8StringEncoding]; | ||
NSString *headerValue = [[NSString alloc] initWithBytes:header.value.bytes | ||
length:header.value.length | ||
encoding:NSUTF8StringEncoding]; | ||
NSMutableArray *headerValueList = headerDict[headerKey]; | ||
if (headerValueList == nil) { | ||
headerValueList = [NSMutableArray new]; | ||
headerDict[headerKey] = headerValueList; | ||
} | ||
[headerValueList addObject:headerValue]; | ||
} | ||
// The C envoy_headers struct can be released now because the headers have been copied. | ||
release_envoy_headers(headers); | ||
return headerDict; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would be great to add tests for these bridging functions before merging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We don't yet have a framework in place for testing at the bridge layer, though @junr03 was talking about exploring this.
return ret; | ||
} | ||
|
||
static EnvoyMutableHeaders *to_ios_mutable_headers(envoy_headers headers) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity, why does this function use snake case when the other helpers use camel case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Our convention has been to use snake case for functions called from the c pathway; camel case for functions called from the Objective-C pathway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it. Maybe we should document that in our contribution guide?
/// | ||
/// - returns: This builder. | ||
@discardableResult | ||
public func addFilterChain(_ filterChain: [RequestFilter]) -> StreamClientBuilder { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably take an individual filter, which gets appended to the underlying chain (rather than replacing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also what are your thoughts on how inter-op ordering will work here with regard to C++ filters?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have a few options:
- platform filters always come first on the request path / last on the response path - straightforward and what we'll do initially.
- platform filters can be appended or prepended - also pretty straightforward.
- represent c++ filters as part of a platform-exposed chain that can be interacted with for arbitrary ordering - more complicated, and something to consider later, if necessary. possible, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm up for doing #1 for MVP 👍
204eb8f
to
0663784
Compare
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Jose Nino <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
Signed-off-by: Mike Schore <[email protected]>
0663784
to
2ca901a
Compare
This pull request has been automatically marked as stale because it has not had activity in the last 7 days. It will be closed in 7 days if no further activity occurs. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions! |
Closing in favor of #940 and upcoming PRs. |
Description: Introduces a mechanism to write platform-native filters that can be linked into Envoy's HTTP filter chain.
Signed-off-by: Mike Schore [email protected]