10

I want to allow downloading a binary file (.p12 file) using ruby's Grape API. This is what I am trying.

get '/download_file' do
  pkcs12 = generate_pkcsfile 
  content_type('application/octet-stream')
  body(pkcs12.der)
end

The equivalent code using ActionController is

begin
  pkcs12 = generate_pkcsfile
  send_data(pkcs12.der,
            :filename => 'filename.p12')
end

The problem is the file downloaded using the API seems to be a text file with a '\ufffd' prefix embedded for every character, whereas the file downloaded using the browser seems to be binary file. How do I use the GRAPE API framework to allow downloading the same file that is downloaded via ActionController's send_data

boboverflow
  • 309
  • 4
  • 10
  • Comparing to the output from a request using ActionController, do you see a difference in the `Content-Type` HTTP header being returned? Do they have different charsets? – Stuart M May 20 '13 at 07:50
  • When you compare API vs browser in the question, is that *actually* "API client plus API route" vs "browser plus ActionController route"? You have four possible combinations, and it is not clear which you have problems with - from your description, could the problem actually be in your test API client? – Neil Slater May 20 '13 at 09:03
  • Right. To clarify, for testing grape API, I am using curl/wget as client and /download_file as endpoint. For testing ActionController, I am using firefox browser and /keys as endpoint. In other words, the endpoints are different, but I want to get the same behavior on both endpoints. I checked the two headers. The browser/keys response header had 'text/html; charset=utf-8' as the content-type, whereas the grape response header had 'application/octet-stream' as the the content type. However switching the grape code to return 'text/html; charset=utf-8' did not change the response body. – boboverflow May 20 '13 at 17:25
  • I cannot replicate the fault using curl (i.e. the code in my answer works as I'd expect using curl). I am however running Grape mounted direct on rackup, and wonder if perhaps some Rails middleware is interfering for you. Have you tried hosting your Grape service directly, without Rails, to rule this out? – Neil Slater May 20 '13 at 20:04

2 Answers2

19

There are issues #412 and #418 have been reported to grape github page. Which are related to return a binary file and override content type.

To return binary format like so:

get '/download_file' do
    content_type "application/octet-stream"
    header['Content-Disposition'] = "attachment; filename=yourfilename"
    env['api.format'] = :binary
    File.open(your_file_path).read
end
1

I think your Grape code is OK, I have tested a variant of it using a browser and a Mac HTTP tool (called GraphicalHTTPClient) that I use to test APIs. I successfully loaded a binary file from disk and transferred it with MIME type 'application/octet-stream' using almost identical code to yours:

  get :download do
    data = File.open('binary_data').read
    content_type 'application/octet-stream'
    body data
  end

I suggest your problem is with the API client and/or the character encoding (as suggested by Stuart M). Although another possibility that occurs to me form our discussion so far, is that some Rack middleware is being triggered incorrectly, and modifying the output from Grape.

Neil Slater
  • 26,512
  • 6
  • 76
  • 94
  • Thanks to all for the responses. I tried switching the grape endpoint to return the same character encoding as send_data; yet I am not seeing any change to the response body. I am considering switching both the ActionController and Grape endpoint code to return Base64 encoded string to guarantee identical behavior for both use cases. – boboverflow May 20 '13 at 17:30
  • I suspect the data needs a `force_encoding` here. If you can reproduce this in a simple Grape app, bring it up on the Grape mailing list. – dB. Jun 19 '13 at 00:09