-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Envoy should be able to preserve header casing in HTTP/1.1 #14363
Comments
Would you be open to designing this in such a way that would preserve the input order as well (or at least allow for preserving input order)? We've seen cases where poorly-written clients/servers can break on header ordering as well as case changes. |
As long as the feature has zero cost when disabled I don't have a strong opinion about how it's done. I think the header formatter per stream with appropriate callbacks from the codec could probably allow order to be modified somehow but it would require a bit more thinking. |
cc @jmarantz |
We can have the following approach to solve this problem:
Still, each filter has to call this a new function instead of the old addCopy. Any thoughts on the above approach? |
Per @mattklein123 we want to achieve zero-cost when the feature is disabled, so I don't think you want to add a new field to the header structures. I also don't think you need/want to modify existing filters. You mainly want to modify the H1 codec to (if the new feature is enabled) add some annotation metadata. One way to do this without adding any existing cost (when disabled) is to add hidden header-map entries that will not serialize, but will store enough bits to decode the header-map values. E.g. let's say I have these headers presented to the codec:
We could leave that in its case-folded form for use in the system but add an extra hidden header to capture the case-preserved names:
You'd modify the deserialization code to populate This doesn't require modifying any filters unless those filters want to add to the case-preservation hidden entry. And it doesn't require increasing the footprint of any existing data structures. |
In the I like the general approach of the |
How about just a |
RE speed of iterating over the comma-separated string? Let's get some numbers with a microbenchmark. Iterating over a comma-separated string-view I think can be made pretty fast. RE order-of-headers; that's already preserved, no? RE new case_preserved_keys_ -- sure but that adds memory footprint even when not in use. I'm also not convinced it would be measurably faster. While you wouldn't need to search for the next comma, you may not get as good memory locality as with a simple comma-split string. This might be hard to capture with a microbenchmark though because often with those everything will be in L1 cache. In any case the dominant cost is probably building the map. And while I'm thinking of it: that map doesn't need to copy any strings. It should actually be a set: envoy/source/common/common/utility.h Line 193 in 2ee9543
RE "persisted across copies"; that would not be my concern. If you did add a new field you'd need to preserve it on copies. |
|
See Slack around here https://envoyproxy.slack.com/archives/C78HA81DH/p1607554081053300. I think we can do this with a header formatter per stream which stores a map. It should be pretty clean, no cost when not used, and cover most cases. |
@mattklein123 - Can you elaborate on this a tad bit and/or confirm my understanding is correct? So a new concept called a "header formatter" that is stored as a member on stream info? A pure virtual class w/ an overridable |
You want to add case-sensitive header names programmatically in a custom C++ filter? Rewriting to ":case-preserve" would work for that I suppose, though you'd need to be careful if you do that a lot to avoid n^2 manipulation of that header's value. Otherwise you'd need to have give the filter access to the formatter, which seems plausible. Matt, this formatter doesn't exist currently, right? |
@jmarantz The concept of the formatter exists in the HTTP codec, but nothing exists that propagate the original casings. I think this would all be easily done if we're just storing a map/set of header casings on I think once we have this all we'd need is to plumb this through to the upstream/downstream codecs and we can use this to power a new kind of header formatter that uses this map/set. |
Ah cool -- this sounds reasonable and better than :case-preserve then. |
Yeah, so per @snowp right now the header formatter exists but it's a static concept. What I'm suggesting is that instead of the formatter being static/set by config for all requests, we allow it to be allocated on a per-stream basis. So we would allow allocating an extension formatter on downstream stream creation, which is attached to the stream end-to-end. Then, on the encoding side it can do whatever it wants. In this case, it would probably internally create some type of map and then reference the map on encoding. This is roughly semantically equivalent to what @snowp is suggesting, though I would suggest doing it the way I'm suggesting vs. stream info, mainly because I think you are going to need extension code that runs on the downstream side no matter what, and I think it would be cleaner to just allow that to own the process end-to-end vs. involving stream info, but I don't feel that strongly about it. |
Pardon my ignorance again here. I think I am following with regards to setting the header key formatter on I was gonna go with just a |
Here is what I'm concretely suggesting but it may need tweaking depending on the requirements. At this point I think you would be better off creating a gdoc with various options that is easier to iterate on:
Now, from your previous description, it seems like you also want to change the state in a filter? For that perhaps your custom formatter can operate on some well known stream metadata? Again, I think I would do a gdoc that clearly lists out end-user requirements, and then looks into implementation options. The hard requirement is this is zero cost (other than possibly some pointer checks) for people not using this feature. |
That was where I got confused about "not involving stream info", because my use case is I have externally defined headers that I set in a filter extension programmatically and I must preserve their casing (i.e. this is more than just request casing). But now that I see that this is a completely new typed extension endpoint that needs to be created, yes, I can choose to have the filter set some state on the stream the formatter references. This is a bit difficult to do across multiple filters, but we'll see. Therefore, instead of writing a preserve-case formatter, we are writing a custom-formatter extension of which anyone can implement preserve-case. I assume to satisfy this ticket itself we also need a preserve-case formatter? (I doubt the original issue opener and others were expecting to have to write custom code to preserve request header case). This became a bit bigger of a task :-) If the requirements get too complicated during impl, definitely will create a gdoc and share it. Otherwise it may be a POC PR sans tests/docs/polish to confirm approach. |
I think once the infrastructure is plumbed in and a preserve-case formatter is supplied, anyone using Envoy would be able to select case-preservation for HTTP 1.* in configuration, without writing any code. If you want to add a case-preserved header in a new filter then you'd have to just make different or extra call from the filter implementation. It's probably worthwhile iterating in a doc first as that can be faster than iterating in code :) |
+1 please do a doc before writing code. This is pretty nuanced and I want to make sure we are all on the same page. Thank you! |
i think that i'm speaking for a lot of people when i say that i am looking forward to this feature to be available. for us, it's not really a convenient solution to change the client to conform with the HTTP 1.1 headers being case-insensitive. |
I've been discussing this with @cretz off-issue. I'm looking into putting a design proposal together on this soon. |
@jtway et. al thanks for discussing this further. I've been meaning to work on this but have some other things on my plate right now. Maintainers, please feel free to reassign as necessary, in case someone ends up being ready to work on this before me. |
I have been working to put together a basic design doc/proposal for this feature. I do believe there are quite a few outstanding questions, but I believe there is enough to further the discussion. |
@jtway thanks that doc looks like a good overview. Please ping me when it's more fleshed out and I can help with the review! Excited to see this issue finally fixed. |
@mattklein123 do you have an example of what you are looking for? |
Briefly scanning over the doc I would love to see
|
@snowp Will look to add those details to the document. That being said, I was hoping for some thoughts from the maintainers here, as there seems to be some preference. Initially I was thinking StreamInfo, but @mattklein123 seems to be discouraging that. |
The first alternative that comes to mind would be to store it on the HCM ActiveStream (one for upstream and one for downstream) and then expose HTTP filter callbacks that allows reading the downstream formatter or setting the upstream formatter. This would allow the HCM to initialize this formatter when desired and expose it to the router filter which would pass it to the upstream codec, and allow the router filter to initialize the formatter based on the upstream formatter and have the HCM read it back when constructing the downstream response. |
Thanks @snowp, completely forgot I have been thinking ActiveStream at one point as well. I will think through this a bit more and update the document. |
Yes I would roughly suggest what @snowp outlines above. What I'm asking for is a more fully fleshed out implementation proposal. What the doc contains now is mostly a summary of my comments in this issue. If it turns out that doing what @snowp outlines above is difficult for some reason, I'm not 100% opposed to attaching a custom formatter to stream info somehow, but it wouldn't be my first choice. The doc should outline the final proposal which might require some limited prototyping. |
Our equipment is distributed all over the world, code changes are convenient, but equipment updates are almost impossible to complete,Looking forward to the release of this feature ! |
I plan on circling back to this, but have been delayed by a few other priorities. |
@jtway I would like to see this closed out so I can potentially work on this. Let me know if you won't get to it soon. |
How soon were you thinking? As I am still figuring out the specifics of how the suggested approach would work, I would be open to you taking this on. This will largely be a "as time permits" task for me, as we have a work around. I'm still relatively new to the Envoy ecosystem, and the suggested approach contained a lot more moving parts than I was thinking it did at first glance. I can definitely find time in the next few business days to spruce up the gdoc, extend with a few more specifics, and most importantly challenges I've encountered. Hopefully that will at least prove helpful. One other, relatively big challenge, is figuring out how we would have this work with HTTP Filters who may inject headers (which would need their case preserved as well) since often Filters directly access the HeaderMap instead of going through the codec. |
I can probably work on this next week, so if you want to clean up the gdoc that would be helpful. |
I will try to make the time to update as much as I can, and flush some areas out by CoB Monday. It no doubt will still be woefully incomplete, but I do hope it will at least save a little time. |
@jtway don't worry about it. I have a good idea of what needs to be done and I will report back! |
Understood. I would very much like to stay involved with further discussion and looking at the PR. I do not know if you plan on updating the document at all, however, if you do, I'll use it to better gauge the level of detail you all are looking for in the future. |
1) Add new stateful header formatter extension point 2) Add preserve case formatter extension Fixes #14363 Signed-off-by: Matt Klein <[email protected]>
1) Add new stateful header formatter extension point 2) Add preserve case formatter extension Fixes #14363 Signed-off-by: Matt Klein <[email protected]>
1) Add new stateful header formatter extension point 2) Add preserve case formatter extension Fixes envoyproxy/envoy#14363 Signed-off-by: Matt Klein <[email protected]> Mirrored from https://github.com/envoyproxy/envoy @ 2a4d97ce66db565d191b42cdf51f4b99edf04f12
1) Add new stateful header formatter extension point 2) Add preserve case formatter extension Fixes envoyproxy/envoy#14363 Signed-off-by: Matt Klein <[email protected]>
@esmet do you or anyone else use this extension in production ? Is the extension considered “production ready”? I assume so since the doc does not say anything about it being beta or experimental. |
1) Add new stateful header formatter extension point; 2) Add preserve case formatter extension; Fixes envoyproxy#14363 Signed-off-by: jiangshantao <[email protected]>
* feat: http: add HTTP/1.1 case preservation (envoyproxy#15619) 1) Add new stateful header formatter extension point; 2) Add preserve case formatter extension; Fixes envoyproxy#14363 Signed-off-by: jiangshantao <[email protected]> * fix: fix Error envoy_cc_library() got unexpected keyword argument: category Signed-off-by: jiangshantao <[email protected]> * fix: source/common/http/http1/settings.cc:39:39: error: unused parameter 'validate_scheme' Signed-off-by: jiangshantao <[email protected]> * feat: preserve case with proper case config Signed-off-by: jiangshantao <[email protected]> Co-authored-by: jiangshantao <[email protected]>
This ticket continues the various discussions that have happened over the years on the topic of header casing in HTTP/1.1
In short, while the spec does not require header casing to be preserved, many legacy applications were written to assume the casing of HTTP headers and may be hard or impossible for some organizations to fix.
I will (soon) be preparing a design doc for this feature. The high-level idea is that we might be able to modify the HTTP1 codec to be aware of the original header casing observed from either downstream clients (when forwarding headers upstream) or from upstream servers (when sending headers downstream) and use that context to format headers, perhaps re-using the existing HttpHeaderFormatter.
As additional context, to support a much simpler use case, I wrote datawire#9 to force (not preserve) header casing on certain configured header keys. This was relatively straight-forward because forcing header values on a fixed set of keys doesn't require complex context to be passed around. Ideally this solution could be replaced by a more comprehensive solution that we come up with here.
Continues: #8463
cc @mattklein123 @snowp
The text was updated successfully, but these errors were encountered: