Skip to content

Response map HTTP filter for custom upstream responses#14221

Closed
esmet wants to merge 8 commits intoenvoyproxy:mainfrom
esmet:response-map
Closed

Response map HTTP filter for custom upstream responses#14221
esmet wants to merge 8 commits intoenvoyproxy:mainfrom
esmet:response-map

Conversation

@esmet
Copy link
Copy Markdown
Contributor

@esmet esmet commented Nov 30, 2020

Support custom upstream responses using a new response_map filter.

The response map filter behaves like the local reply code, but on the HTTP filter chain instead. This allows for modification of responses generated both locally and upstream.

The motivation and implementation strategy for this change is captured in https://docs.google.com/document/d/1THyRnepx89FbK2VVaqoEHXv2knbm_lsLx2XgDZ7FxDw/edit?usp=sharing

Commit Message:
Additional Description:
Risk Level: medium (new filter, refactored common code)
Testing: unit tests done, integration tests still needed.
Docs Changes: NEEDED
Release Notes: NEEDED
Platform Specific Features:
Fixes #12883

John Esmet and others added 2 commits November 30, 2020 18:10
Signed-off-by: John Esmet <john.esmet@gmail.com>
The original ResponseMap implementation largely copied from local_reply.cc
with some specializations. Now, LocalReply is a ResponseMap specialization
whose configuration comes from type whose from an object on the
HttpConnectionManager.

Signed-off-by: John Esmet <john.esmet@gmail.com>
@repokitteh-read-only
Copy link
Copy Markdown

Hi @esmet, welcome and thank you for your contribution.

We will try to review your Pull Request as quickly as possible.

In the meantime, please take a look at the contribution guidelines if you have not done so already.

🐱

Caused by: #14221 was opened by esmet.

see: more, trace.

@repokitteh-read-only
Copy link
Copy Markdown

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to api/envoy/.
API shepherd assignee is @lizan
CC @envoyproxy/api-watchers: FYI only for changes made to api/envoy/.

🐱

Caused by: #14221 was opened by esmet.

see: more, trace.

@esmet esmet marked this pull request as draft November 30, 2020 21:24
Signed-off-by: John Esmet <john.esmet@gmail.com>
@esmet esmet changed the title [DRAFT] Response map HTTP filter for custom upstream responses Response map HTTP filter for custom upstream responses Nov 30, 2020
Http::ResponseHeaderMap& response_headers,
StreamInfo::StreamInfo& stream_info, Http::Code& code, std::string& body,
absl::string_view& content_type) const PURE;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Still needs comments

using ResponseMapPtr = std::unique_ptr<ResponseMap>;

/**
* Access log filter factory that reads from proto.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This comment could be improved

licenses(["notice"]) # Apache 2

# L7 HTTP filter that implements response modifications using matchers.
# Public docs: docs/root/configuration/http_filters/response_map_filter.rst
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This doesn't exist yet.

*
* Not guaranteed to be called. In particular, if Envoy sends a local reply with HTTP 426
* as an upgrade response for HTTP/1.0 requests, we may never decode any of the client's
* original headers, because the request was rejected at the initial HTTP/1.0 line.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This comment might be better off above the original decodeHeaders function declaration.


const ResponseMap::ResponseMapPtr& response_map = *response_map_;

// Fill in new_body and new_content_type using the response map rewrite.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Extend this comment to mention that we capture the rewritten code here per the method signature, but we don't actually do anything with the new value.

// Encoding buffer may be null here even if we saw data in encodeData above. This
// happens when sendLocalReply sends a response downstream. By adding encoded data
// here, we do successfully override the sendLocalReply body, but it's not clear
// how or why.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We should get some clarity around this condition here and rewrite the comment to explain what's going on instead of asking questions.

return Http::FilterTrailersStatus::Continue;
}
void setDecoderFilterCallbacks(Http::StreamDecoderFilterCallbacks& callbacks) override {
decoder_callbacks_ = &callbacks;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We never use this, so it should probably be removed.

const absl::optional<std::string>& responseCodeDetails() const override {
return response_code_details_;
}
void setResponseCode(uint32_t code) override { response_code_ = code; }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I would be in favor of moving this small addition to another pull request if others think that's better.

@alyssawilk alyssawilk self-assigned this Dec 1, 2020
// [#next-free-field: 6]
message ResponseMapper {
// Filter to determine if this mapper should apply.
accesslog.v3.AccessLogFilter filter = 1 [(validate.rules).message = {required: true}];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@lizan can I ask you to do an API pass before I dig into the code?
I think a lot of the components of access logs apply but not all do - not sure how much API refactor we want to do here and how much we can just do via standard metadata matcher.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@snowp can you weigh in here re: the new matcher API fits here? I think the access log filter works well for this purpose but just want to double check.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I would think that this could be accomplished with the new matching API, though we would most likely spend some time to reach parity in terms of supporting all the same conditionals that the access log filter supports. The matching API would offer some benefit in terms of better performance when lots of matchers are used, due to being able to structure it more as a tree than as a list of complex matchers.

I think this falls into the category that we'd use the matching API if it was available today but since that work is ongoing it becomes a question of time lines. Maybe @mattklein123 has thoughts on whether we're okay with relying on the access log filters here.

I could imagine the access log filter eventually being subsumed by the new matching work, but that would be a longer term thing

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, I'd initially hoped the new matcher code could be used, since using access filters just strikes me as suboptimal and I'd like the perf wins. Looks like it'd also do away with much of the shared code, so probably the way to go (if we opt to have a standalone filter for per-route) is stick with this for now, and move both the HCM and filter matchers over to the new API when it's more feature complete.

esmet added 3 commits December 1, 2020 22:13
…t test

Signed-off-by: John Esmet <john.esmet@gmail.com>
… string

Signed-off-by: John Esmet <john.esmet@gmail.com>
Signed-off-by: John Esmet <john.esmet@gmail.com>
DataSource text_format_source = 5;

// The empty format string.
bool empty_format = 6 [(validate.rules).bool = {const: true}];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't see where this is used, shall we just relax the min_len: 1 constraint on text_format if we want an empty format string?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We use it here https://github.com/envoyproxy/envoy/pull/14221/files#diff-7a3f06d418ccdf0de0f51a473b6d3450baa5faf9ac8362aa6fe33bc1284b5152R27

We could just relax the constraint, but my initial idea was to avoid making changes to an existing field and figured the explicit "empty" option was clear enough.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would slightly prefer relax the existing one, or we can have a field fixed_text which just do what PlainStringFormatterImpl does.

@esmet esmet marked this pull request as ready for review December 3, 2020 17:49
esmet added 2 commits December 3, 2020 21:35
Signed-off-by: John Esmet <john.esmet@gmail.com>
Signed-off-by: John Esmet <john.esmet@gmail.com>
Copy link
Copy Markdown
Member

@lizan lizan left a comment

Choose a reason for hiding this comment

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

can we split the matcher / substitution part to another PR?

DataSource text_format_source = 5;

// The empty format string.
bool empty_format = 6 [(validate.rules).bool = {const: true}];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I would slightly prefer relax the existing one, or we can have a field fixed_text which just do what PlainStringFormatterImpl does.

// [#next-free-field: 6]
message ResponseMapper {
// Filter to determine if this mapper should apply.
accesslog.v3.AccessLogFilter filter = 1 [(validate.rules).message = {required: true}];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@snowp can you weigh in here re: the new matcher API fits here? I think the access log filter works well for this purpose but just want to double check.

@esmet
Copy link
Copy Markdown
Contributor Author

esmet commented Dec 4, 2020

can we split the matcher / substitution part to another PR?

Yes, will do. I will also look into replacing empty_format with fixed_text and passing that text to PlainStringFormatterImpl.

@esmet
Copy link
Copy Markdown
Contributor Author

esmet commented Dec 4, 2020

@lizan #14276 for the fixed_text etc changes

@@ -0,0 +1,89 @@
syntax = "proto3";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is not where extension config should live, move to api/envoy/extensions/filters/http/response_map/v3

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The intention here is for this to be non-extension config (for source/common/response_map/*) and to have separate extension config for the response map filter (source/extensions/http/filters/response_map/...).

Is this idea correct or misguided?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I looked harder at how the API is organized and I think I understand. My feeling now is that this should live under api/envoy/extensions/common/reponse_map since I intend for it to be shared, eventually, by both the HCM and ResponseMap filter. Does that make sense?


// The configuration to filter and change HTTP responses.
// [#next-free-field: 6]
message ResponseMapper {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This isn't different from ResponseMapper in HCM, why not just use it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The idea is to have a common config that could be shared across HCM and any other use case (like the response map http filter). I think we should deprecate the response mapper on the HCM to use this proto instead. I didn't do that here because I wasn't sure of the right/approved migration path. What do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I captured my thoughts here https://docs.google.com/document/d/1THyRnepx89FbK2VVaqoEHXv2knbm_lsLx2XgDZ7FxDw and added a link to the PR description

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks, can you give us comment access to the doc?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

}

// The configuration to customize HTTP responses read by Envoy.
message ResponseMap {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is same as LocalReplyConfig in HCM as well?

Copy link
Copy Markdown
Contributor Author

@esmet esmet Dec 8, 2020

Choose a reason for hiding this comment

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

Yep. From the comment above, I intended for there to be a common proto shared across use cases and that we should (eventually) migrate HCM to use this common type. I would appreciate thoughts/guidance on a migration strategy.

Copy link
Copy Markdown
Member

@lizan lizan left a comment

Choose a reason for hiding this comment

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

Thanks for putting in the doc, let's discuss about design options there first and come back to the actual implementation.

The major concern I have is this configuration model as a filter could be very confusing, as the local reply by routers and filters are also going through encoder filter so it will affected by both LocalReplyConfig and this filter. Having these configuration in separate places doesn't seems a good design. cc @alyssawilk @envoyproxy/api-shepherds

@htuch
Copy link
Copy Markdown
Member

htuch commented Dec 9, 2020

Could we just add an option to the HCM response mapper to also apply to upstream responses?

@lizan
Copy link
Copy Markdown
Member

lizan commented Dec 9, 2020

Could we just add an option to the HCM response mapper to also apply to upstream responses?

Yeah I'm leaning to this as well.

@mattklein123
Copy link
Copy Markdown
Member

Yeah I'm leaning to this as well.

+1. I was just about to start looking through the doc, but I'm strongly against this amount of duplication and it seems like we can just extend the mapper to work with upstream responses also. Can we potentially re-orient the doc to that solution and then talk through any issues that come up in the doc?

@mattklein123 mattklein123 self-assigned this Dec 9, 2020
@esmet
Copy link
Copy Markdown
Contributor Author

esmet commented Dec 9, 2020

Yeah I'm leaning to this as well.

+1. I was just about to start looking through the doc, but I'm strongly against this amount of duplication and it seems like we can just extend the mapper to work with upstream responses also. Can we potentially re-orient the doc to that solution and then talk through any issues that come up in the doc?

I'll look into how we could do this in the HCM and update the doc with the new research. When I originally designed this code, I was thinking that the filter chain would be invoked for all sendLocalReply calls but @lizan showed me that this isn't true in all cases (in particular, stream timeouts etc).

@esmet
Copy link
Copy Markdown
Contributor Author

esmet commented Dec 9, 2020

I updated the doc. It now tries to focus much more on the problem statement and possible solutions and less on the particulars of this pull request or even code in general. I will now start doing more code research and will update the doc with those findings as they become relevant.

@alyssawilk
Copy link
Copy Markdown
Contributor

I think that having the HCM matcher be able to alter upstream responses is worth doing, especially for the common case where some server has global response tweaks they want to apply which can't be done via existing config knobs, but there's still the problem of per-route config to deal with. That feels like a better fit for the RouteEntryImplBase::perFilterConfig which currently requires an explicit filter, though if the @envoyproxy/api-shepherds have a way to generalize that it could address that particular concern.

@mattklein123
Copy link
Copy Markdown
Member

That feels like a better fit for the RouteEntryImplBase::perFilterConfig which currently requires an explicit filter, though if the @envoyproxy/api-shepherds have a way to generalize that it could address that particular concern.

Yeah agreed that we should have per-route config in addition to generic matching, though I think this is also an issue for local replies, since in many cases a local reply may be generated when a route is available. I would need to look into it in more detail but I wonder if we could just have a non-filter key for override config that could be looked up?

@alyssawilk
Copy link
Copy Markdown
Contributor

If you're OK with a non-filter key that works for me. Alternately @esmet suggested offline that (with some possible tweaking to the per-filter-config code) we could arguably use the HCM network filter name (it is a filter, just not L7 =P) instead of an arbitrary string. WDYT?

@mattklein123
Copy link
Copy Markdown
Member

If you're OK with a non-filter key that works for me. Alternately @esmet suggested offline that (with some possible tweaking to the per-filter-config code) we could arguably use the HCM network filter name (it is a filter, just not L7 =P) instead of an arbitrary string. WDYT?

Either SGTM. I agree the latter sounds interesting and we could have a generic config message that might eventually allow overriding various pieces of HCM behavior on a per-route basis.

@lizan
Copy link
Copy Markdown
Member

lizan commented Dec 9, 2020

I think that having the HCM matcher be able to alter upstream responses is worth doing, especially for the common case where some server has global response tweaks they want to apply which can't be done via existing config knobs, but there's still the problem of per-route config to deal with. That feels like a better fit for the RouteEntryImplBase::perFilterConfig which currently requires an explicit filter, though if the @envoyproxy/api-shepherds have a way to generalize that it could address that particular concern.

If we're not implementing the response mapper in a filter, then it can be a field in Route message and shouldn't be part of typed_per_filter_config anyway? Then it won't use RouteEntryImplBase::perFilterConfig either.

@alyssawilk
Copy link
Copy Markdown
Contributor

if we're not doing it in a filter, don't we want it all in the HCM/FM for behavioral consistency? it's not clear to me if we'd need the route specific logic if Envoy sent a local reply late in the response process, but the FM is the only entity which sees the direct responses.

lizan pushed a commit that referenced this pull request Dec 15, 2020
…t_format (#14276)

This change is motivated by #14221 where we use a SubstitutionFormatString as a way to define custom HTTP response body rewrites.

Commit Message: formatter: add text_format_source, relax minimum string length on text_format in SubstitutionFormatString
Additional Description: The relaxed field validation on text_format now allows a user to replace something with nothing, e.g. to replace a non-empty HTTP response body with an empty one. The text_format_source field allows for a DataSource to be used to supply text inside of providing it inline.

Risk Level: low (new fields)
Testing: unit test needed for text_format_source
Docs Changes: NEEDED
Release Notes: NEEDED
Platform Specific Features:

Signed-off-by: John Esmet <john.esmet@gmail.com>
@esmet
Copy link
Copy Markdown
Contributor Author

esmet commented Jan 4, 2021

This is still on my plate. The next thing that I plan to do is to try moving the response mapping code out of an HTTP extension and into the HCM itself.

Base automatically changed from master to main January 15, 2021 23:02
@esmet
Copy link
Copy Markdown
Contributor Author

esmet commented Feb 5, 2021

I submitted a new draft PR that implements this behavior in the FilterManager #14926 feedback welcome.

I'm keeping branch/PR unchanged so we have the HTTP filter implementation to compare to, and preserve history.

@github-actions
Copy link
Copy Markdown

github-actions bot commented Mar 8, 2021

This pull request has been automatically marked as stale because it has not had activity in the last 30 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!

@github-actions github-actions bot added the stale stalebot believes this issue/PR has not been touched recently label Mar 8, 2021
@github-actions
Copy link
Copy Markdown

This pull request has been automatically closed because it has not had activity in the last 37 days. Please feel free to give a status update now, ping for review, or re-open when it's ready. Thank you for your contributions!

@github-actions github-actions bot closed this Mar 15, 2021
@alexandru-ersenie
Copy link
Copy Markdown

@esmet : The way i look at it is that your work is still in progress, and there is no way to control the error pages for example, returned by the upstream.

As we are using envoy as a proxy to the Istio Mesh, my understanding is we'd have to wait a little bitt longer, am i correct to assume that ?

Thanks !

@esmet
Copy link
Copy Markdown
Contributor Author

esmet commented Apr 16, 2021

@alexandru-ersenie yes, this work is still in progress and I need to get it to a place where we achieve consensus around how it will be implemented. I will hopefully be able to pick this up again soon.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api stale stalebot believes this issue/PR has not been touched recently waiting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Custom upstrem/backend response

7 participants