Response map HTTP filter for custom upstream responses#14221
Response map HTTP filter for custom upstream responses#14221esmet wants to merge 8 commits intoenvoyproxy:mainfrom
Conversation
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>
|
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. |
Signed-off-by: John Esmet <john.esmet@gmail.com>
| Http::ResponseHeaderMap& response_headers, | ||
| StreamInfo::StreamInfo& stream_info, Http::Code& code, std::string& body, | ||
| absl::string_view& content_type) const PURE; | ||
|
|
| using ResponseMapPtr = std::unique_ptr<ResponseMap>; | ||
|
|
||
| /** | ||
| * Access log filter factory that reads from proto. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
I would be in favor of moving this small addition to another pull request if others think that's better.
| // [#next-free-field: 6] | ||
| message ResponseMapper { | ||
| // Filter to determine if this mapper should apply. | ||
| accesslog.v3.AccessLogFilter filter = 1 [(validate.rules).message = {required: true}]; |
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
@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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
…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}]; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
I would slightly prefer relax the existing one, or we can have a field fixed_text which just do what PlainStringFormatterImpl does.
Signed-off-by: John Esmet <john.esmet@gmail.com>
Signed-off-by: John Esmet <john.esmet@gmail.com>
lizan
left a comment
There was a problem hiding this comment.
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}]; |
There was a problem hiding this comment.
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}]; |
There was a problem hiding this comment.
@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.
Yes, will do. I will also look into replacing |
| @@ -0,0 +1,89 @@ | |||
| syntax = "proto3"; | |||
There was a problem hiding this comment.
This is not where extension config should live, move to api/envoy/extensions/filters/http/response_map/v3
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
This isn't different from ResponseMapper in HCM, why not just use it?
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
I captured my thoughts here https://docs.google.com/document/d/1THyRnepx89FbK2VVaqoEHXv2knbm_lsLx2XgDZ7FxDw and added a link to the PR description
There was a problem hiding this comment.
Thanks, can you give us comment access to the doc?
| } | ||
|
|
||
| // The configuration to customize HTTP responses read by Envoy. | ||
| message ResponseMap { |
There was a problem hiding this comment.
This is same as LocalReplyConfig in HCM as well?
There was a problem hiding this comment.
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.
lizan
left a comment
There was a problem hiding this comment.
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
|
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. |
+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). |
|
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. |
|
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. |
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? |
|
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. |
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 |
|
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. |
…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>
|
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. |
|
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. |
|
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! |
|
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! |
|
@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 ! |
|
@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. |
Support custom upstream responses using a new
response_mapfilter.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