-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
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
Set response headers based on Rack version #2355
Set response headers based on Rack version #2355
Conversation
cd40b2f
to
138a771
Compare
I like this better than #2356. rack/rack#1592 is clear that this is changing to lowercase in Rack 3. One option would be to major-version increment Grape as part of this PR and do the same as in Rack. |
4511cf6
to
97c2348
Compare
97c2348
to
608bd18
Compare
Do you need something adding to this PR for this? I also pushed fixing the integration spec paths and updated changelog. |
Let's see if other maintainers have an opinion. If we major increment the version here we can make the backwards incompatible change. I am 50/50 on what's best. |
@ioquatix would like your eyes and opinion here if you have a moment? |
@schinery looks like we may need to do extra work to support Rack 1.x. |
I will merge this unless I hear from other maintainers not to. @schinery Let's increment the version to 1.9 as part of this PR since at the very least it's a big change, and add a section to UPGRADING? |
8c10933
to
72ec447
Compare
72ec447
to
cd71b14
Compare
@dblock hopefully done all the PR feedback correctly, with the exception of "I think we need more text explaining what happens in what code. Give an example of an API that sets one of these headers, or explain when they are set?" as not 100% sure what you want for this. Do you have an example in mind? |
Also, just going through the README, and wondering if the following needs updating: There is also https://github.com/ruby-grape/grape#header-case-handling, which this PR hasn’t altered as it has only changed the two hardcoded headers to fix error cascading. Should this be handled in separate PR as this is what #2254 is on about? |
@dblock just trying to get a sense of the other changes needed to resolve #2254 to see if we do it in this PR, so far I have: 1: Update 2: Fix the specs, like https://github.com/ruby-grape/grape/blob/master/spec/grape/request_spec.rb#L121, so they are Rack version specific. Am guessing this would mean that they would go in the Rack integration specs like we've added in this PR? 3: Update the README, such as https://github.com/ruby-grape/grape#header-case-handling, so that it has changes for both header casings. 4: Don't think we need to do anything for the Not sure what else, partly as this is my first time truly digging around the code. Anything else you think is needed? |
Does also make me wonder... would it be an idea to leave version 1 of Grape (1.x branch off of master) supporting Rack < 3 and create a version 2 of Grape (current master) that supports Rack >= 3. There would be more overhead for maintaining etc depending how long you want to maintain version 1, but the code would be cleaner without the Rack version detection code in it. Happy to continue with #2355 though, let me know your thoughts. |
@dblock more for #2355 (comment) I threw this PR together quick just to look at what might need changing (minus the README changes), especially which specs would need updating. There were 5 specs that need the header in the right case based on what Rack version is used in the test run. |
I would be totally fine with incrementing major to 2.0 as part of this PR, and lock it at Rack >= 3, instead of trying to do both if you prefer? I don't love the idea of also maintaining a 1.x. Are you interested in doing so? It's just a 1.x branch and releasing. |
It just feels a bit cleaner. As you saw in schinery#1 (which again was more to highlight than a solution) some of the issues are in the testing, plus I guess you also have to have update the docs to support both Rack versions.
Not really interested either, but I guess then you have something if you need to patch 1.x. in the future, and I guess then you could then just release a 1.8.1 or 1.9.0 release with an updated README etc, with any new features etc going in the 2.x version. If it is decided that this is approach to take then is this what needs to happen?
Does it feel like we need a second opinion on this too? |
One thing I was wrong about... it wasn't Rails 7.1 that bumped Rack to 3, as the deps in the Gemfile.lock are as follows:
However, I've just been playing about with locking to Rack 3 in a branch and I get this error using
So, if Grape was locked to Rack 3 it looks like potentially that version might only work with Rails 7.1. Which means I'm now flip flopping between whether Rack should be locked in Grape or not. Suspect not... |
I've updated schinery#1 with the additional changes I think are needed to support both Rack versions in Grape. I haven't addressed your additional comments in that PR, partly as my brain is tied in knots. |
I feel like my brain is also tied in knots over this, so let's try to merge one thing! Let's make the changes as you proposed originally, support both Rack 2.x and 3.x where in Rack 3.x all headers are lowercase. Take your changes in schinery#1 into this PR. Can the code be simplified by making |
Merged schinery#1 in. Will ponder the "a case-insensitive structure" for the request headers, would be ace if there was a way to do it but there is nothing screaming out at me at the moment. |
This builds a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Getting there ...
lib/grape/request.rb
Outdated
@@ -47,7 +47,11 @@ def build_headers | |||
end | |||
|
|||
def transform_header(header) | |||
-header[5..].split('_').each(&:capitalize!).join('-') | |||
if Gem::Version.new(Rack.release) < Gem::Version.new('3') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This executes in a tight loop, which is bad for performance. You want this check outside. We also have it in a few places. It should look like this:
if Grape::Rack.rack3?
def transform_header(header)
-header[5..].split('_').map(&:capitalize).join('-')
end
else
def transform_header(header)
-header[5..].tr('_', '-').downcase
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added .rack3?
method and done this change, although I've added it to Grape
rather than Grape::Rack
otherwise there was a load of Rack errors raised because we would need to change all the Rack references to ::Rack
.
Can do this though if really needs to be Grape::Rack
spec/grape/api_spec.rb
Outdated
@@ -689,7 +689,7 @@ class DummyFormatClass | |||
'example' | |||
end | |||
put '/example' | |||
expect(last_response.headers['Content-Type']).to eql 'text/plain' | |||
expect(last_response.headers[rack_versioned_headers[:content_type]]).to eql 'text/plain' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we get rid of rack_versioned_headers
and instead do Grape::HTTP::Headers::CONTENT_TYPE
?
spec/support/headers_helpers.rb
Outdated
module Spec | ||
module Support | ||
module Helpers | ||
def rack_versioned_headers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Get rid of this class, use Grape::HTTP::Headers::CACHE_CONTROL
and friends.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only issue with this is what to do in the specs that are testing with non Grape::HTTP::Headers
headers, such as Grape-Likes-Symbolic
or X-Access-Token
, as these need to be Rack versioned in the specs.
I've removed the headers helper though and put these inline, so they are defined in whatever specs needed them
820e1ab
to
0a88e94
Compare
Yeah, it doesn't... irb(main):001:0> hash = ActiveSupport::HashWithIndifferentAccess.new(a: 1)
=> {"a"=>1}
irb(main):002:0> hash["A"]
=> nil
irb(main):003:0> Whether something like this would work. I'm not sure what code changes would be required though. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to merge and let it sit.
# check if user is admin or not | ||
# as an example get a token from request and check if it's admin or not | ||
raise Grape::Exceptions::Validation.new(params: @attrs, message: 'Can not set Admin only field.') unless request.headers['X-Access-Token'] == 'admin' | ||
raise Grape::Exceptions::Validation.new(params: @attrs, message: 'Can not set Admin only field.') unless request.headers[access_header] == 'admin' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could use x_access_token_header
here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, tried that, but got...
NameError:
undefined local variable or method `x_access_token_header' for #<#<Class:0x0000ffff8f8f9b18>:0x0000ffff928d97e8 @attrs=[:admin_field], @option=true, @required=false, @scope=#<Grape::Validations::ParamsScope:0x0000ffff8f8f96b8 @element=nil, @element_renamed=nil, @parent=nil, @api=#<Class:0x0000ffff8f8f9898>, @optional=false, @type=Hash, @group=nil, @dependent_on=nil, @declared_params=nil, @index=nil>, @fail_fast=false, @allow_blank=nil>
which was annoying, as don't like the defining it twice either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This generally looks okay to me.
The biggest issue is, people trying to support both Rack 2 and Rack 3 will need to be careful about the assertions. Direct assertions against the hash can be problematic. It's important to have a proper normalization step in assertions IMHO, as the expectation should be "A header named X has a value Y", but the case of X can depend on the HTTP version, server, etc. In Rails, there were many instances of header key and value case which had ossified to whatever Rack (the gem) was generating, even thought that was only a subset of valid possible results.
I know this was a pretty annoying change in some regards, but overall it will simplify things and aligns the code with the HTTP specification more closely in a way which makes it easier to know that code is doing the right thing. e.g. rack/rack-test#229
|
||
#### Headers | ||
|
||
As per [rack/rack#1592](https://github.com/rack/rack/issues/1592) Rack 3.0 is enforcing the HTTP/2 semantics, and thus treats all headers as lowercase. Starting with Grape 1.9.0, headers will be cased based on what version of Rack you are using. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rack 3.0 is enforcing the HTTP/2 semantics, and thus treats all headers as lowercase. Starting with Grape 1.9.0, headers will be cased based on what version of Rack you are using.
Rack 3 is following the HTTP/2+ semantics which require header names to be lower case. To avoid compatibility issues, starting with Grape 1.9.0, headers will be cased based on what version of Rack you are using.
@@ -42,6 +42,10 @@ def self.deprecator | |||
@deprecator ||= ActiveSupport::Deprecation.new('2.0', 'Grape') | |||
end | |||
|
|||
def self.rack3? | |||
Gem::Version.new(::Rack.release) >= Gem::Version.new('3') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you are just trying to detect whether to use lower case headers or not, I suggest a slightly different approach:
LOWER_CASE_HEADERS = Rack::CONTENT_TYPE == "content-type"
You could then use that directly or in a method.
header 'Content-Length', nil | ||
header 'Transfer-Encoding', nil | ||
header 'Cache-Control', 'no-cache' # Skips ETag generation (reading the response up front) | ||
header Grape::Http::Headers::CONTENT_LENGTH, nil |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can use Rack::CONTENT_LENGTH
if that suits your use case.
Additional comments in #2362 |
#2354