0
votes

How do i restrict my API to accept & respond only json format on Rails & Grape, i've try format :json on my Grape controller and (for example) i can access it on example.com/api/v1/ping.json, but i also can access it via example.com/api/v1/ping.xml, example.com/api/v1/ping.foobar, and the list of extensions goes on...

The things i would like to do is, throwing error on example.com/api/v1/ping.not_json_extensions

Im using:

  • rails (4.1.1)
  • grape (0.7.0)

/config/routes.rb

mount API::Base => '/api'

/controllers/api/base.rb

module API
 class Base < Grape::API
  mount API::V1::Base
 end
end

/controllers/api/v1/base.rb

module API
 module V1
  class Base < Grape::API
   format :json
   mount API::V1::Ping
  end
 end

end

/controllers/api/v1/ping.rb

module API
 module V1
  class Ping < Grape::API
    include API::V1::Defaults
    desc 'Returns pong.'
    get :ping do
      { ping: params[:pong] || 'pong' }
    end
  end
 end

end

1

1 Answers

2
votes

Looking at Grape's source code, it seems this was the intended behaviour but a change to prevent memory leaks effectively broke it.

You can implement the correct behaviour "manually" by adding an explicit check to your API class (in /controllers/api/base.rb):

before do
  # Make sure the format specified by the request extension is one
  # we support
  parts = request.path.split('.')

  if parts.size > 1
    extension = parts.last

    if !extension.eql? 'json'
      throw :error, {
        status: 406,
        message: "The requested format '#{extension}' is not supported."
      }
    end
  end
end

This code is copied pretty much verbatim from Grape's source (in lib/grape/middleware/formatter.rb) and is how Grape itself checks the extension used in the request.

In that file, negotiate_content_type is responsible for checking whether the requested format is one supported by the API, and clearly gives priority to the request extension. However the method that parses the extension from the URI, format_from_extension, also checks whether the format is supported and returns nil if it isn't, as though there were no extension at all. As a result negotiate_content_type will never trigger an error if the request's extension specifies an unsupported format, even though it clearly was meant to do so.

You can "fix" this by changing the code at formatter.rb:109 from

# avoid symbol memory leak on an unknown format
return extension.to_sym if content_type_for(extension)

to simply

return extension.to_sym

The comment suggests the code was written this way for a reason, though, so proceed with caution.