Skip to content

Commit 5bf2d12

Browse files
committed
Merge pull request #252 from simulacre/http_405
adds 405 response for methods not supported on a resource instead of 404
2 parents c4b2692 + eaeb55e commit 5bf2d12

File tree

4 files changed

+114
-2
lines changed

4 files changed

+114
-2
lines changed

CHANGELOG.markdown

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Fixes
2020
* [#181](https://github.com/intridea/grape/pull/181): Fix: Corrected JSON serialization of nested hashes containing `Grape::Entity` instances - [@benrosenblum](https://github.com/benrosenblum).
2121
* [#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).
2222
* [#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).
23+
* [#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)
2324

2425
0.2.1 (7/11/2012)
2526
=================

README.markdown

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,50 @@ redirect "/new_url"
411411
redirect "/new_url", :permanent => true
412412
```
413413

414+
## Allowed Methods
415+
416+
When you add a route for a resource, a route for the HTTP OPTIONS
417+
method will also be added. The response to an OPTIONS request will
418+
include an Allow header listing the supported methods.
419+
420+
``` ruby
421+
class API < Grape::API
422+
get '/counter' do
423+
{ :counter => Counter.count }
424+
end
425+
426+
params do
427+
requires :value, :type => Integer, :desc => 'value to add to counter'
428+
end
429+
put '/counter' do
430+
{ :counter => Counter.incr(params.value) }
431+
end
432+
end
433+
```
434+
435+
``` shell
436+
curl -v -X OPTIONS http://localhost:3000/counter
437+
438+
> OPTIONS /counter HTTP/1.1
439+
>
440+
< HTTP/1.1 204 No Content
441+
< Allow: OPTIONS, GET, PUT
442+
```
443+
444+
445+
If a request for a resource is made with an unsupported HTTP method, an
446+
HTTP 405 (Method Not Allowed) response will be returned.
447+
448+
``` shell
449+
curl -X DELETE -v http://localhost:3000/counter/
450+
451+
> DELETE /counter/ HTTP/1.1
452+
> Host: localhost:3000
453+
>
454+
< HTTP/1.1 405 Method Not Allowed
455+
< Allow: OPTIONS, GET, PUT
456+
```
457+
414458
## Raising Exceptions
415459

416460
You can abort the execution of an API method by raising errors with `error!`.

lib/grape/api.rb

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ def initialize
414414
self.class.endpoints.each do |endpoint|
415415
endpoint.mount_in(@route_set)
416416
end
417+
add_head_not_allowed_methods
417418
@route_set.freeze
418419
end
419420

@@ -422,5 +423,41 @@ def call(env)
422423
end
423424

424425
reset!
426+
427+
private
428+
429+
# For every resource add a 'OPTIONS' route that returns an HTTP 204 response
430+
# with a list of HTTP methods that can be called. Also add a route that
431+
# will return an HTTP 405 response for any HTTP method that the resource
432+
# cannot handle.
433+
def add_head_not_allowed_methods
434+
allowed_methods = Hash.new{|h,k| h[k] = [] }
435+
resources = self.class.endpoints.map do |endpoint|
436+
endpoint.options[:app] && endpoint.options[:app].respond_to?(:endpoints) ?
437+
endpoint.options[:app].endpoints.map(&:routes) :
438+
endpoint.routes
439+
end
440+
resources.flatten.each do |route|
441+
allowed_methods[route.route_compiled] << route.route_method
442+
end
443+
444+
allowed_methods.each do |path_info, methods|
445+
allow_header = (["OPTIONS"] | methods).join(", ")
446+
unless methods.include?("OPTIONS")
447+
@route_set.add_route( proc { [204, { 'Allow' => allow_header }, []]}, {
448+
:path_info => path_info,
449+
:request_method => "OPTIONS"
450+
})
451+
end
452+
not_allowed_methods = %w(GET PUT POST DELETE PATCH HEAD) - methods
453+
not_allowed_methods.each do |bad_method|
454+
@route_set.add_route( proc { [405, { 'Allow' => allow_header }, []]}, {
455+
:path_info => path_info,
456+
:request_method => bad_method
457+
})
458+
end
459+
end
460+
end
461+
425462
end
426463
end

spec/grape/api_spec.rb

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -301,8 +301,8 @@ def app; subject end
301301
send(verb, '/example')
302302
last_response.body.should eql verb == 'head' ? '' : verb
303303
# Call it with a method other than the properly constrained one.
304-
send(verbs[(verbs.index(verb) + 1) % verbs.size], '/example')
305-
last_response.status.should eql 404
304+
send(used_verb = verbs[(verbs.index(verb) + 1) % verbs.size], '/example')
305+
last_response.status.should eql used_verb == 'options' ? 204 :405
306306
end
307307
end
308308

@@ -315,6 +315,36 @@ def app; subject end
315315
last_response.status.should eql 201
316316
last_response.body.should eql 'Created'
317317
end
318+
319+
it 'should return a 405 for an unsupported method' do
320+
subject.get 'example' do
321+
"example"
322+
end
323+
put '/example'
324+
last_response.status.should eql 405
325+
last_response.body.should eql ''
326+
end
327+
328+
specify '405 responses should include an Allow header specifying supported methods' do
329+
subject.get 'example' do
330+
"example"
331+
end
332+
subject.post 'example' do
333+
"example"
334+
end
335+
put '/example'
336+
last_response.headers['Allow'].should eql 'OPTIONS, GET, POST'
337+
end
338+
339+
it 'should add an OPTIONS route that returns a 204 and an Allow header' do
340+
subject.get 'example' do
341+
"example"
342+
end
343+
options '/example'
344+
last_response.status.should eql 204
345+
last_response.body.should eql ''
346+
last_response.headers['Allow'].should eql 'OPTIONS, GET'
347+
end
318348
end
319349

320350
describe 'filters' do

0 commit comments

Comments
 (0)