Carl Crawley
Carl Crawley

Reputation:

Using Send_File to a Remote Source (Ruby on Rails)

In my app, I have a requirement that is stumping me.

I have a file stored in S3, and when a user clicks on a link in my app, I log in the DB they've clicked the link, decrease their 'download credit' allowance by one and then I want to prompt the file for download.

I don't simply want to redirect the user to the file because it's stored in S3 and I don't want them to have the link of the source file (so that I can maintain integrity and access)

It looks like send_file() wont work with a remote source file, anyone recommend a gem or suitable code which will do this?

Upvotes: 12

Views: 6393

Answers (3)

Demelziraptor
Demelziraptor

Reputation: 1555

Full image download method using temp file (tested rails 3.2):

def download
  @image = Image.find(params[:image_id])

  open(@image.url) {|img|
    tmpfile = Tempfile.new("download.jpg")
    File.open(tmpfile.path, 'wb') do |f| 
      f.write img.read
    end 
    send_file tmpfile.path, :filename => "great-image.jpg"
  }   
end

Upvotes: 4

klochner
klochner

Reputation: 8125

You can read the file from S3 and write it locally to a non-public directory, then use X-Sendfile (apache) or X-Accel-Redirect (nginx) to serve the content.

For nginx you would include something like the following in your config:


            location /private {
                                 internal;
                                 alias /path/to/private/directory/;
            }

Then in your rails controller, you do the following:


   response.headers['Content-Type'] = your_content_type
   response.headers['Content-Disposition'] = "attachment; filename=#{your_file_name}"
   response.headers['Cache-Control'] =  "private"
   response.headers['X-Accel-Redirect'] = path_to_your_file
   render :nothing=>true

A good writeup of the process is here

Upvotes: 3

LucaM
LucaM

Reputation: 2846

You would need to stream the file content to the user while reading it from the S3 bucket/object.

If you use the AWS::S3 library something like this may work:

 send_file_headers!( :length=>S3Object.about(<s3 object>, <s3 bucket>)["content-length"], :filename=><the filename> )
 render :status => 200, :text => Proc.new { |response, output|
   S3Object.stream(<s3 object>, <s3 bucket>) do |chunk|
     output.write chunk
   end
 }

This code is mostly copied form the send_file code which by itself works only for local files or file-like objects

N.B. I would anyhow advise against serving the file from the rails process itself. If possible/acceptable for your use case I'd use an authenticated GET to serve the private data from the bucket.

Using an authenticated GET you can keep the bucket and its objects private, while allowing temporary permission to read a specific object content by crafting a URL that includes an authentication signature token. The user is simply redirected to the authenticated URL, and the token can be made valid for just a few minutes.

Using the above mentioned AWS::S3 you can obtain an authenticated GET url in this way:

 time_of_exipry = Time.now + 2.minutes
 S3Object.url_for(<s3 object>, <s3 bucket>,
                  :expires => time_of_exipry)

Upvotes: 9

Related Questions