Skip to content
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

Closed
wants to merge 17 commits into from
Closed

wip: platform extension filter poc #883

wants to merge 17 commits into from

Conversation

goaway
Copy link
Contributor

@goaway goaway commented Jun 5, 2020

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]

@goaway
Copy link
Contributor Author

goaway commented Jun 8, 2020

@junr03 format checks pass for me locally - any idea how I can debug what's going on?

@rebello95
Copy link
Contributor

rebello95 commented Jun 8, 2020

Looks like buildifier failed based on the CI log. Do you have a diff when you do:

buildifier library/common/extensions/filter/BUILD

@junr03
Copy link
Member

junr03 commented Jun 8, 2020

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.

Copy link
Member

@junr03 junr03 left a 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(
Copy link
Member

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;
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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;
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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.

Copy link
Contributor Author

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 {
Copy link
Member

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

Copy link
Contributor Author

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;
Copy link
Member

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.

Copy link
Contributor Author

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).

Copy link
Member

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

Copy link
Contributor Author

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),
Copy link
Member

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?

Copy link
Contributor Author

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

@goaway goaway force-pushed the ms/platform-filter branch 3 times, most recently from d0d1416 to 3ed68fa Compare June 18, 2020 01:15
@junr03
Copy link
Member

junr03 commented Jun 23, 2020


/**
* 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.
Copy link
Contributor

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

Comment on lines +35 to +38
- name: envoy.filters.http.platform_extension
typed_config:
"@type": type.googleapis.com/envoymobile.extensions.filters.http.platform_extension.Bridging
name: PlatformExample
Copy link
Contributor

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: {}");
Copy link
Contributor

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.
Copy link
Contributor

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*/) {
Copy link
Contributor

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

Copy link
Contributor Author

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.

Comment on lines 28 to +29
func onRequestHeaders(_ headers: RequestHeaders, endStream: Bool)
-> FilterHeaderStatus<RequestHeaders>
-> UInt32
Copy link
Contributor

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)

Copy link
Contributor Author

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;
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure 👍

Comment on lines +27 to +47
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;
}
Copy link
Contributor

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

Copy link
Contributor Author

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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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 {
Copy link
Contributor

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)

Copy link
Contributor

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?

Copy link
Contributor Author

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:

  1. platform filters always come first on the request path / last on the response path - straightforward and what we'll do initially.
  2. platform filters can be appended or prepended - also pretty straightforward.
  3. 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.

Copy link
Contributor

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 👍

@goaway goaway force-pushed the ms/platform-filter branch from 204eb8f to 0663784 Compare June 25, 2020 13:31
goaway and others added 17 commits June 30, 2020 09:24
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: 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]>
@goaway goaway force-pushed the ms/platform-filter branch from 0663784 to 2ca901a Compare June 30, 2020 02:22
@stale
Copy link

stale bot commented Jul 7, 2020

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!

@stale stale bot added the stale label Jul 7, 2020
@goaway
Copy link
Contributor Author

goaway commented Jul 7, 2020

Closing in favor of #940 and upcoming PRs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants