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
Consider introducing env['rack.request.headers']
for original headers before transforming them.
#1841
Comments
I don't think this is helpful for performance in HTTP/1, since headers can be mixed case. I agree that it is pretty much impossible to change rack to move away from CGI keys, since the minor performance benefits of doing so are far less than the cost in the loss of backwards compatibility. Since we have to support CGI keys anyway, supporting original request headers is always going to be strictly slower. If we do choose to include this in SPEC (and I don't think we should), I think it should be optional. |
Maybe we should consider application level feature flags, like
|
The idea of feature flags are interesting. How do you see application/libraries adopting this new feature? Or the feature is mostly for rack backward compatibility. I guess what I'm asking here is: if we didn't care about backwards compatibility and we can change all existing code, how would this new env help us, and how would applications/libraries benefit from it? |
I discussed this with @tenderlove while I was in Seattle and he was positive on the idea. I think the advantage of feature flags is it allows us to experiment and make more aggressive changes to Rack without breaking backwards compatibility. Servers could also expose server-specific features and if flags are un-supported it could be indicated clearly at app-load time. The biggest challenge would be how that looks to middleware, for example, fundamentally changing |
+1 on this idea. It’s way too painful to parse out the original headers in application code, and to do it correctly today is a dark art |
Agreed with @ianks, I often find myself trying to backwards-engineer an original header and it's often less than straightforward! |
So just to clarify, I think there are two ways forward: Headers under separate key
|
I'm fine with |
Can you clarify this statement because the blended format headers do not intersect with any standard rack headers as defined by the SPEC, the only consideration is headers which contain If we were willing to wear the long term cost, I would advise us reserving a namespace like |
If we go down the @nateberkopec @ianks should we aim for this to be a required or optional feature of Rack? How much of a problem is it? |
If the proposal is "Blended For "(upper case can be considered invalid HTTP header)", upper case is completely valid for request headers in HTTP 1, so how do you propose to handle those headers? Remember that the vast majority of rack usage is with HTTP 1 and that is not likely to change anytime soon. For "Could include RACK_", switching the
I don't think it makes sense to switch from We should only break backwards compatibility if there is a huge benefit in doing so, such as lower case response headers allowing for O(1) response header access and avoiding the need to wrap headers in middleware. Rearranging the deck chairs by switching the names of |
We can do both styles for compatibility. I'm not suggesting we should do it, just stating that we in theory could do it.
We should normalise all incoming request headers as lower case.
Yes, for sure, I don't think this is ultimately a good solution, but it's an option to consider.
We can continue to follow CGI style naming conventions but I believe it's more complex than it needs to be and introduces additional complexities like how to handle
I think there are two key gains: request headers and response headers have the same format is easy for users to (1) understand and (2) consume. I think if we can find a middle ground of I wasn't really planning this for Rack 3.0 but I'd be happy to put together a PR to this effect. I think it keeps everyone happy. But I think we should consider bigger picture if that's the direction we want. Maybe to put it another way, if we were building |
IMO, that's the wrong way to look at this issue. This would be an appropriate attitude if backwards compatibility was not considered important. I think that backwards compatibility is of paramount importance, and should only be broken in cases where there are major advantages to do so, not just to make things slightly nicer than they are now.
|
I'm aware of this position on compatibility and totally respect it, but I think it's all about finding a middle ground . Without exploring what the options look like it's hard to know what that is or even whether the compatibility trade offs are worth it. In other words: "We considered all the options and their various trade offs and decided this was the best approach for the following reasons." |
I was working on You can see we have constants for some headers, like: Lines 20 to 21 in cd4b3c9
But these are only valid for response headers, they are not valid You can see the side effect of this implementation in Line 199 in cd4b3c9
We have a mismatch between "request environment" which isn't quite headers and response headers. There are other instances of this problem, e.g. Line 26 in cd4b3c9
Might I add that I think these examples support a blended approach to headers, although I'll admit it will be a large pain. Maybe something we can work towards for Rack 4? |
Some constants are for some things, other constants are for other things. There are comments stating what each constant is for. Maybe my opinion of you is too high, but I'm surprised you would find this "very confusing". That sounds like an disingenuous exaggeration to push your agenda :).
I'll grant these are different. There is a necessary difference in that the request environment must contain non-header data. That the header names are mangled differently for request headers and response headers is not a necessary difference, but backwards compatibility is much more important than consistency in this case, IMO.
The Rack SPEC specifies that IMO,
I don't think it's a good tradeoff in Rack 3, and I doubt I'll consider it a good tradeoff in Rack 4. Optional |
I think it's confusing to have constants in the same module which look the same, but are used in very different contexts. Even the best engineer with a poor interface can make mistakes. I think the best we can hope to do is take these experiences and use them to shape the developer UX of Rack to be the best it can be. Anyway just make it clear if it isn't already my agenda is to:
Why should these not work? request = Rack::Request.new(env)
request.get_header('authorization')
request.set_header('authorization', 'token')
response = Rack::Response.new
response.set_header('authorization', 'token')
response.get_header('authorization') To me, a header has a very specific definition as per the RFCs we follow. If |
I'm OK with using these as examples of what not to do in the future. :)
These are noble goals. However, it's frustrating to me that backwards compatibility concerns are nowhere on your agenda, as they are on the forefront of my agenda.
I'm not sure why you would be opposed to adding more accurate names if your agenda is to make things easier to understand. It doesn't fix the problem of the old method name being inaccurate, but it at least allows people to move forward with better names. But I won't push to add aliases if you are against them.
Absent backwards compatibility concerns, I agree with you that they should. I just don't think changing |
I'm fine with adding aliases but I'm not sure it makes sense when someone can just write |
Another way to word this would be: do we want to see adoption for With that being said, as annoying as the CGI style headers are, if the performance hit is significant it may not be worth requiring the feature.
Does anyone have any benchmarks for this? Would be good to have some numbers for typical apps, then include some of apps that have a couple nasty 4K-8K size header values. |
The cost for falcon will be close to zero. The cost for puma might be a little bit:
Overhead would be minor. Probably in the grand scheme of things irrelevant. |
I want Rack to look pretty much the same as it looks currently. It works well and does not need major backwards incompatible changes. Hopefully the lower case response header keys will be the last major backwards incompatible change we make.
The issue here is not necessarily in the size of headers, but in the number of headers. This will probably increase the number of allocations required by around 50%, and time spent populating the environment by 80%. How much of a difference that makes depends on the server, request, and application.
Even for HTTP 1, where you have to create a lowercase version of the request header keys, since they aren't required to be in lower case? Seems odd that you would be already creating a lowercase version of the request header keys now, since there would be no use for it.
Depends on the request I would think. Obviously if you are talking about a request that takes 100ms to produce a response, any changes to the environment are going to be minor in comparison. I'm guessing requiring One thing that I don't think has been considered yet is that request headers are mutable. Currently, if you change a header in the environment, other code that looks for the header always get the updated value, since the header value is stored in a single place. Once you store the header value in two places, it becomes impractical to guarantee that you'll see the updated value, considering that a lot of current code modifies the environment directly and not through a method. This is an acceptable risk for a feature you need to opt into. I'm not sure it's acceptable to turn on by default, since it exposes all applications to issues where some code updates the header value in only |
For Even for the current CGI-style headers, I think immutability of the header values would be a good thing. I'd be curious to know how badly things today would break if we froze the values... |
I think it's reasonable the keys and values are immutable. But |
Header keys should already be immutable, since ruby will use a frozen copy of a string as a hash key, if the key is given as an unfrozen string. I'm not sure whether making header values immutable is worth the backwards compatibility impact, but I think it's something we can consider. I agree with @ioquatix that I'm not sure there is a backwards compatible way of introducing |
To clarify, it seems like the problem you are concerned about is the duplicated headers if we have both It's true this is an important issue, but I think the answer is pretty easy, at least in the short term, users should be consulting the The question then becomes what is the point of This is why I suggested maybe a better way is to entirely replace the
Because any of these options gives us a clean slate, maybe it's best we consider what that should look like, rather than trying to find "compatible" but ultimately not practical solutions. |
That's pretty much it. If you considered headers immutable, this wouldn't be an issue, but since the headers are mutable, this is an issue.
OK. That keeps compatibility now. Of course, if you do eventually switch to checking
How common is it for rack applications to proxy requests to other HTTP servers (as opposed to calling another rack application with the same environment)? My gut feeling is that's a rare case that we don't need to optimize for. It's true that the
IMO, the cost of breaking backwards compatibility in this area is not going to be worth the benefits. Ask yourself, who really benefits from changing from CGI If a user/application is already doing the mangling themselves, then switching to a different mangling/storage is breaking things without a benefit. If a user/application is not already doing the mangling themselves, then they are probably using a web framework which already handles the mangling for them. Changing to a different mangling/storage is then going to be no different to the user/application, and just complicate the related web framework. I suppose you could argue that while this doesn't make things easier for existing users/applications, it makes things easier for new users/applications. I'll concede that, though I don't think there is a major difference. However, Ruby/Rack is a mature ecosystem, and we should prioritize backwards compatibility over making things easier for new users, especially in cases like this where the benefits of the change are minor. |
I'm now almost completely convinced that we shouldn't introduce |
@nateberkopec @ianks I introduced/fixed |
HTTP/2 is an important protocol and improves the HTTP semantics in a number of key ways, by reducing the complexity of the request meta-data (including path, scheme, authority, etc) as headers.
I'd like to see us migrate away from CGI style
HTTP_FOO_BAR
headers (fromfoo-bar:
) but this is so ingrained into Rack that it would be almost impossible to change.My proposal is to start changing tack little by little, the first step would be to introduce
env['rack.request.headers']
which are the raw request headers. We can leave it at that, or we can specify it should follow HTTP/2 semantics and include pseudo-headers too (my preference).In any case, this allows systems which need it, to access the raw underlying headers, while systems that follow the CGI
env['HTTP_FOO_BAR']
continue to work as expected.The text was updated successfully, but these errors were encountered: