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

OPTIONS requests do not go through the middleware stack #414

Closed
wants to merge 6 commits into from

Conversation

benschwarz
Copy link

I want to extend the response that an API will return for OPTIONS requests — I started by writing a middleware:

class MyAPI < Grape::API
  use Grape::Middleware::OptionsException
  mount ApiExample
end
class Grape::Middleware::OptionsException < Grape::Middleware::Base
  def call(env)
    raise env.inspect
  end
end

A get request will through the error:

~ ⚡  curl -I  http://localhost:9292/super/special\?id\=1                                                         
HTTP/1.1 500 Internal Server Error 
Content-Type: text/html
Content-Length: 87978
Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-12-25)
Date: Wed, 29 May 2013 01:49:50 GMT
Connection: Keep-Alive

but an options request will not:

~ ⚡  curl -I -X OPTIONS http://localhost:9292/super/special\?id\=1                                               
HTTP/1.1 204 No Content 
Allow: OPTIONS, GET, HEAD
Server: WEBrick/1.3.1 (Ruby/1.9.3/2012-12-25)
Date: Wed, 29 May 2013 01:50:22 GMT
Connection: close

Any thoughts, @mbleigh ?

@dblock
Copy link
Member

dblock commented May 29, 2013

Check out /~https://github.com/intridea/grape/blob/master/lib/grape/api.rb#L504, it will respond to OPTIONS. There's a way to disable this behavior with a do_not_route_options! in the API declaration.

Hope this helps. Please do use the mailing list for questions.

@dblock dblock closed this May 29, 2013
@benschwarz
Copy link
Author

@dblock, so you're saying that if I use:

class MyAPI < Grape::API
  do_not_route_options!
  use Grape::Middleware::OptionsException

  mount ApiExample
end

Then an options request should go through to the middleware, right? (It doesn't)

@benschwarz
Copy link
Author

Ok, additional information @dblock

Using do_not_route_options! does indeed render status 405, however, the request does not get passed through to middleware.

… it also still sets the Allow: header too.

@dblock
Copy link
Member

dblock commented May 29, 2013

I'll reopen as a bug. Do you think you could write a test within Grape for something like this? I'll take a look.

@benschwarz
Copy link
Author

Hmm, I can try…
I guess I can register a middleware for a given test and have it fail when the middleware isn't hit?

@dblock
Copy link
Member

dblock commented May 29, 2013

Anything that fails that you expect to succeed :)

@benschwarz
Copy link
Author

@dblock Talking over what should happen a little more:

  • OPTIONS is enabled by default
  • Disabling OPTIONS using do_not_route_options! should:
    • Let the request go through the middleware stack
    • If nothing collects the response, a 405 will be issued

Anything else?

@benschwarz
Copy link
Author

ping @dblock

Should I have a stab at implementing this?

@dblock
Copy link
Member

dblock commented May 31, 2013

Don't hesitate!

Added BadMethod middleware to handle unsupported methods
Added middlewares to autoload
Added allowed_methods_for_route to Grape::Middleware::Base
@benschwarz
Copy link
Author

@dblock, I've added some commits that move OPTIONS support down to the middleware level—

In addition to this, I've also added a BadMethod middleware, for throwing 405 errors when a given method isn't available for a route.

I haven't added these to the default stack in endpoint.rb because the test suite doesn't appear to execute them correctly? Can you please review?

Also, existing specs covered that a Content-type of text/plain should be added to 405 requests… but I'm not sure why the content-type should be altered?

/ping @mbleigh

@@ -0,0 +1,12 @@
class Grape::Middleware::BadMethod < Grape::Middleware::Base
Copy link
Member

Choose a reason for hiding this comment

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

I think BadMethod may be a poor naming choice (totally inspired by existing code). What this middleware does is check whether the method is allowed or not, I would rename it to just Method.

Copy link
Author

Choose a reason for hiding this comment

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

Agree. Lets rename it to Method

@dblock
Copy link
Member

dblock commented Jun 2, 2013

This is very much on the right track. Needless to say I much rather have a middleware implementation for this than the homegrown route mounting one. If you had all specs passing, and added a line to CHANGELOG, I would merge this. If you need help with something, like the question you had on inserting this into the middleware stack, glad to help, just say that you're stuck with a specific thing.

The content-type is something that was definitely by design. See #343.

Added implementation for setting the content-type to text/plain for 405 response.
allowed_methods = allowed_methods_for_route

unless allowed_methods.include? env['REQUEST_METHOD']
[405, { 'Allow' => allowed_methods.join(', '), 'Content-Type' => 'text/plain' }, []]
Copy link
Author

Choose a reason for hiding this comment

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

Covered #343

@dblock
Copy link
Member

dblock commented Jun 3, 2013

Looks closer. Did you look at the spec failures?

@benschwarz
Copy link
Author

I haven't covered the spec failures yet… I'm relying on the allowed_methods_for_route method from Middleware::Base… but it feels pretty "unracky" for a middleware to need to pop up to the application to lookup something.

…What do you think?

@dblock
Copy link
Member

dblock commented Jun 3, 2013

You could setup the middleware explicitly at construction time instead and modularize allowed_methods_for_route. But I am not sold that it is useful at this stage, and not writing the code I don't know where the underwater mines are. I think you should leave it this way and think about refactoring it further along - what you have now is already much better than what we have here, it's worth finishing.

@benschwarz
Copy link
Author

Understood. I'll ship the first cut. Thanks!

@dblock
Copy link
Member

dblock commented Aug 6, 2013

Bump. I still want this!

@benschwarz
Copy link
Author

I abandoned this branch because… it didn't really work. Middlewares should not be run with any knowledge of other middlewares up the stack — introspecting the routes in the way that I was attempting wouldn't work consistently because the middleware would need to be loaded / required / ordered in such a way that it was the first middleware on the stack, which I think is a good sign that something wasn't quite right.

@karlfreeman karlfreeman mentioned this pull request Nov 4, 2013
@dblock
Copy link
Member

dblock commented Nov 7, 2013

Given #498, what do you all think I should do with this issue?

@benschwarz
Copy link
Author

Given that there is a working OPTIONS implementation now… then we can probably close this.

@dblock
Copy link
Member

dblock commented Nov 7, 2013

Closing.

@dblock dblock closed this Nov 7, 2013
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants