Skip to content
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

incorrect version accept header does not return a 406 response #1101

Closed
wants to merge 1 commit into from

Conversation

elliotlarson
Copy link
Contributor

If you've configured Grape to use accept header versioning with strict
equal to true, Grape should return a 406 response when making a request
with an incorrect version header. However, the system was not
responding with a 406 when using the header 'Accept:application/xml'.

@dblock
Copy link
Member

dblock commented Aug 13, 2015

Add to CHANGELOG and squash the commits please?

end

def media_type
@media_type ||= header.best_of available_media_types
Copy link
Member

Choose a reason for hiding this comment

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

I would add a pair of ( ), so header.best_of(available_media_types), just more readable. There're a couple of other instances like this,

@elliotlarson
Copy link
Contributor Author

Thanks for your code review, dblock! I've added these updates in.

@dblock
Copy link
Member

dblock commented Aug 14, 2015

This looks good. Can I have someone else confirm that I am not missing anything here, cause this bug is a bit big to be true :) Maybe @dm1try? Thx.

@elliotlarson
Copy link
Contributor Author

Yeah, that makes sense. Thanks for your time on this.

env['api.version'] = Regexp.last_match[2]
# weird that Grape::Middleware::Formatter also does this
env['api.format'] = Regexp.last_match[3]
elsif versions.size > 0
Copy link
Member

Choose a reason for hiding this comment

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

this condition and expectation seems strange to me.
if size of predefined versions more than zero then fail with parsed "vendor or version not found"

as we can see the versions has a predefined value: options[:versions] => [:v1]
so if you remove in the context of your spec version value :v1 then it'll fail again.
I mean it's not expected behaviour.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. This was a bit of a kludgy fix on my part. I tried to make the smallest possible change to get the desired result. Now that I've gone down the road of doing a bit of refactoring, I feel like this is the wrong place for this logic. I've updated the code a bit to reflect what I think is probably a better approach.

@dm1try
Copy link
Member

dm1try commented Aug 15, 2015

Anyway the original behaviour was implemented here

see the #available_media_types, it returns sum of "prepared vendor media-types ["application/vnd.vendor-v2+xml", "application/vnd.vendor-v2", "application/vnd.vendor-v1+xml", "application/vnd.vendor-v1" ...]
and defaut media types["application/xml", "application/json", "application/json", "application/octet-stream", "text/plain"..]
this spec describes why default media types are returned too, seems like it's a desired behaviour (too much responsibility for the "versioner-header" middleware btw)

so ATM it's can be easily fixed by adding unless strict? in this line (use default content types only in "non-strict" mode)

maybe a better way is removing the logic which allow to work with default content types from this middleware at all but it's a breaking change. (or move this to base middleware)

@dblock
Copy link
Member

dblock commented Aug 17, 2015

@elliotlarson Can you please see @dm1try's comments above?

@dm1try What do you think of the code change itself, better? worse? no different?

@dm1try
Copy link
Member

dm1try commented Aug 17, 2015

@dblock definitely better!

If you've configured Grape to use accept header versioning with strict
equal to true, Grape should return a 406 response when making a request
with an incorrect version header.  However, the system was not
responding with a 406 when using the header 'Accept:application/xml'.

Adding relevant line to changelog.

Refactoring the middleware version header class:
- Breaking up the before method into more manageable pieces, removing some
of the conditional (cyclomatic) complexity
- fixing some line lengths
- removing some local variables from methods
@elliotlarson
Copy link
Contributor Author

@dm1try Thanks for taking the time to review and provide feedback. I agree that there is a bit of muddled responsibility in this middleware. This could use some more time and refactoring. My hope is to fix this bug with a reasonable, incremental improvement to the code, and then perhaps dig in a bit further after that's out of the way and see if there is a good way to untangle the responsibilities.

I've refactored my fix a bit. I feel like returning a 406 if there's no version or vendor in the accept header belongs further up in the code than the place where I stuck it. If you have strict set, and you're not passing in a version header, the very first strict_header_checks method should field that. So, I've removed my original fix and I've added some checking code into here instead.

Also, the original code had some logic for media type header checking if strict was set. I feel like these are sort of moot checks if strict is applied. If it's applied and we're passing in a header like Accept: application/*, we don't need to return an error about media type ranges not being allowed since the bigger issue is that there isn't a valid version header. The update I've added in reflects that opinion.

Anyway, I hope this is a better solution that meets with your approval. I look forward to your feedback.

expect(exception.message).to include('Accept header must not contain ranges ("*").')
end
end

Copy link
Member

Choose a reason for hiding this comment

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

For the record, would you be so kind to explain why this is no longer something we need?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, sorry, I tried to explain this in the third paragraph above, but this may need a bit more explanation.

The existing code is doing some checking to make sure that you're not passing in media type ranges. However, these checks are only happening in the case when strict is set to true. If strict is set to true and you're passing in an accept header like Accept:application/*, it's my contention that returning an error about not allowing media ranges is less important that returning an error about not passing in a correct version/vendor header.

In other words, with strict set to true, I think using Accept:application/* should result in:

406: API vendor or version not found.
instead of:
406: Accept header must not contain ranges ("*").

... because the larger issue is that the vendor/version accept header is missing.

As I was refactoring the code, I placed the version/vender header checking mechanism before the media range checks. After I did this, it became clear that these media range checks were never getting exercised because the version vendor checks were always returning the 406 error first. So, pulling out the media range checks and the related specs seemed prudent.

Of course, there may be some untested/un-documented outcome that I'm not seeing here related to media type accept headers. Beyond the README and the specs, is there some kind of written spec or standard that you're intending to follow that might provide more guidance here?

Copy link
Member

Choose a reason for hiding this comment

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

This makes perfect sense, thanks.

Copy link
Member

Choose a reason for hiding this comment

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

👍

@dblock
Copy link
Member

dblock commented Aug 18, 2015

@dm1try Would you mind making calls around merging this? I don't feel like I'm qualified :) Thanks.

@dm1try
Copy link
Member

dm1try commented Aug 18, 2015

@elliotlarson this is great, thanks!
@dblock feel free to merge this

@dblock
Copy link
Member

dblock commented Aug 18, 2015

Merged via d7718f0, thank you.

@dblock dblock closed this Aug 18, 2015
@elliotlarson
Copy link
Contributor Author

No problem. Thanks again @dblock and @dm1try for your time reviewing and providing feedback!

@dblock
Copy link
Member

dblock commented Aug 19, 2015

Thanks for hanging along @elliotlarson, great job!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants