Skip to content

Commit

Permalink
Merge pull request #252 from simulacre/http_405
Browse files Browse the repository at this point in the history
adds 405 response for methods not supported on a resource instead of 404
  • Loading branch information
dblock committed Oct 2, 2012
2 parents c4b2692 + eaeb55e commit 5bf2d12
Show file tree
Hide file tree
Showing 4 changed files with 114 additions and 2 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Fixes
* [#181](https://github.com/intridea/grape/pull/181): Fix: Corrected JSON serialization of nested hashes containing `Grape::Entity` instances - [@benrosenblum](https://github.com/benrosenblum).
* [#203](https://github.com/intridea/grape/pull/203): Added a check to `Entity#serializable_hash` that verifies an entity exists on an object - [@adamgotterer](https://github.com/adamgotterer).
* [#208](https://github.com/intridea/grape/pull/208): `Entity#serializable_hash` must also check if attribute is generated by a user supplied block - [@ppadron](https://github.com/ppadron).
* [#252](https://github.com/intridea/grape/pull/252): Resources that don't respond to a requested HTTP method return 405 (Method Not Allowed) instead of 404 (Not Found) [@simulacre](https://github.com/simulacre)

0.2.1 (7/11/2012)
=================
Expand Down
44 changes: 44 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,50 @@ redirect "/new_url"
redirect "/new_url", :permanent => true
```

## Allowed Methods

When you add a route for a resource, a route for the HTTP OPTIONS
method will also be added. The response to an OPTIONS request will
include an Allow header listing the supported methods.

``` ruby
class API < Grape::API
get '/counter' do
{ :counter => Counter.count }
end

params do
requires :value, :type => Integer, :desc => 'value to add to counter'
end
put '/counter' do
{ :counter => Counter.incr(params.value) }
end
end
```

``` shell
curl -v -X OPTIONS http://localhost:3000/counter

> OPTIONS /counter HTTP/1.1
>
< HTTP/1.1 204 No Content
< Allow: OPTIONS, GET, PUT
```


If a request for a resource is made with an unsupported HTTP method, an
HTTP 405 (Method Not Allowed) response will be returned.

``` shell
curl -X DELETE -v http://localhost:3000/counter/

> DELETE /counter/ HTTP/1.1
> Host: localhost:3000
>
< HTTP/1.1 405 Method Not Allowed
< Allow: OPTIONS, GET, PUT
```

## Raising Exceptions

You can abort the execution of an API method by raising errors with `error!`.
Expand Down
37 changes: 37 additions & 0 deletions lib/grape/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ def initialize
self.class.endpoints.each do |endpoint|
endpoint.mount_in(@route_set)
end
add_head_not_allowed_methods
@route_set.freeze
end

Expand All @@ -422,5 +423,41 @@ def call(env)
end

reset!

private

# For every resource add a 'OPTIONS' route that returns an HTTP 204 response
# with a list of HTTP methods that can be called. Also add a route that
# will return an HTTP 405 response for any HTTP method that the resource
# cannot handle.
def add_head_not_allowed_methods
allowed_methods = Hash.new{|h,k| h[k] = [] }
resources = self.class.endpoints.map do |endpoint|
endpoint.options[:app] && endpoint.options[:app].respond_to?(:endpoints) ?
endpoint.options[:app].endpoints.map(&:routes) :
endpoint.routes
end
resources.flatten.each do |route|
allowed_methods[route.route_compiled] << route.route_method
end

allowed_methods.each do |path_info, methods|
allow_header = (["OPTIONS"] | methods).join(", ")
unless methods.include?("OPTIONS")
@route_set.add_route( proc { [204, { 'Allow' => allow_header }, []]}, {
:path_info => path_info,
:request_method => "OPTIONS"
})
end
not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - methods
not_allowed_methods.each do |bad_method|
@route_set.add_route( proc { [405, { 'Allow' => allow_header }, []]}, {
:path_info => path_info,
:request_method => bad_method
})
end
end
end

end
end
34 changes: 32 additions & 2 deletions spec/grape/api_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -301,8 +301,8 @@ def app; subject end
send(verb, '/example')
last_response.body.should eql verb == 'head' ? '' : verb
# Call it with a method other than the properly constrained one.
send(verbs[(verbs.index(verb) + 1) % verbs.size], '/example')
last_response.status.should eql 404
send(used_verb = verbs[(verbs.index(verb) + 1) % verbs.size], '/example')
last_response.status.should eql used_verb == 'options' ? 204 :405
end
end

Expand All @@ -315,6 +315,36 @@ def app; subject end
last_response.status.should eql 201
last_response.body.should eql 'Created'
end

it 'should return a 405 for an unsupported method' do
subject.get 'example' do
"example"
end
put '/example'
last_response.status.should eql 405
last_response.body.should eql ''
end

specify '405 responses should include an Allow header specifying supported methods' do
subject.get 'example' do
"example"
end
subject.post 'example' do
"example"
end
put '/example'
last_response.headers['Allow'].should eql 'OPTIONS, GET, POST'
end

it 'should add an OPTIONS route that returns a 204 and an Allow header' do
subject.get 'example' do
"example"
end
options '/example'
last_response.status.should eql 204
last_response.body.should eql ''
last_response.headers['Allow'].should eql 'OPTIONS, GET'
end
end

describe 'filters' do
Expand Down

0 comments on commit 5bf2d12

Please sign in to comment.