Using send_file to download a file from Amazon S3?

Ruby on-RailsRubyRuby on-Rails-3Amazon S3Ruby on-Rails-3.2

Ruby on-Rails Problem Overview


I have a download link in my app from which users should be able to download files which are stored on s3. These files will be publicly accessible on urls which look something like

https://s3.amazonaws.com/:bucket_name/:path/:to/:file.png

The download link hits an action in my controller:

class AttachmentsController < ApplicationController
  def show
    @attachment = Attachment.find(params[:id])
    send_file(@attachment.file.url, disposition: 'attachment')
  end
end

But I get the following error when I try to download a file:

ActionController::MissingFile in AttachmentsController#show

Cannot read file https://s3.amazonaws.com/:bucket_name/:path/:to/:file.png
Rails.root: /Users/user/dev/rails/print

Application Trace | Framework Trace | Full Trace
app/controllers/attachments_controller.rb:9:in `show'

The file definitely exists and is publicly accessible at the url in the error message.

How do I allow users to download S3 files?

Ruby on-Rails Solutions


Solution 1 - Ruby on-Rails

You can also use send_data.

I like this option because you have better control. You are not sending users to s3, which might be confusing to some users.

I would just add a download method to the AttachmentsController

def download
  data = open("https://s3.amazonaws.com/PATTH TO YOUR FILE") 
  send_data data.read, filename: "NAME YOU WANT.pdf", type: "application/pdf", disposition: 'inline', stream: 'true', buffer_size: '4096' 
end 

and add the route

get "attachments/download"

Solution 2 - Ruby on-Rails

Keep Things Simple For The User

I think the best way to handle this is using an expiring S3 url. The other methods have the following issues:

  • The file downloads to the server first and then to the user.
  • Using send_data doesn't produce the expected "browser download".
  • Ties up the Ruby process.
  • Requires an additional download controller action.

My implementation looks like this:

In your attachment.rb
def download_url
  S3 = AWS::S3.new.buckets[ 'bucket_name' ] # This can be done elsewhere as well,
                                            # e.g config/environments/development.rb
  url_options = { 
    expires_in:                   60.minutes, 
    use_ssl:                      true, 
    response_content_disposition: "attachment; filename=\"#{attachment_file_name}\""
  }

  S3.objects[ self.path ].url_for( :read, url_options ).to_s
end
  
In your views
<%= link_to 'Download Avicii by Avicii', attachment.download_url %>

That's it.


If you still wanted to keep your download action for some reason then just use this:

In your attachments_controller.rb

def download
  redirect_to @attachment.download_url
end

Thanks to guilleva for his guidance.

Solution 3 - Ruby on-Rails

In order to send a file from your web server,

  • you need to download it from S3 (see @nzajt's answer) or

  • you can redirect_to @attachment.file.expiring_url(10)

Solution 4 - Ruby on-Rails

I have just migrated my public/system folder to Amazon S3. Solutions above help but my app accepts different kinds of documents. So if you need the same behavior, this helps for me:

@document = DriveDocument.where(id: params[:id])
if @document.present?
  @document.track_downloads(current_user) if current_user
  data = open(@document.attachment.expiring_url)
  send_data data.read, filename: @document.attachment_file_name, type: @document.attachment_content_type, disposition: 'attachment'
end

The file is being saved in the attachment field of DriveDocument object. I hope this helps.

Solution 5 - Ruby on-Rails

The following is what ended up working well for me. Getting the raw data from the S3 object and then using send_data to pass that on to the browser.

Using the aws-sdk gem documentation found here http://docs.aws.amazon.com/AWSRubySDK/latest/AWS/S3/S3Object.html

full controller method

def download
  AWS.config({
    access_key_id: "SECRET_KEY",
    secret_access_key: "SECRET_ACCESS_KEY"
  })

  send_data( 
    AWS::S3.new.buckets["S3_BUCKET"].objects["FILENAME"].read, {
      filename: "NAME_YOUR_FILE.pdf", 
      type: "application/pdf", 
      disposition: 'attachment', 
      stream: 'true', 
      buffer_size: '4096'
    }
  )
end

Solution 6 - Ruby on-Rails

> How do I allow users to download S3 files?

If you're able to set some metadata on the file BEFORE you upload it to S3 instead of trying to patch it when the user wants to download it later, then this solution is much simpler:

https://stackoverflow.com/a/24297799/763231

> If you are using fog then you can do something like this: > > has_attached_file :report, > fog_file: lambda { |attachment| > { > content_type: 'text/csv', > content_disposition: "attachment; filename=#{attachment.original_filename}", > } > } > > If you are using Amazon S3 as your storage provider, then something > like this should work: > > has_attached_file :report > s3_headers: lambda { |attachment| > { > 'Content-Type' => 'text/csv', > 'Content-Disposition' => "attachment; filename=#{attachment.original_filename}", > } > }

Solution 7 - Ruby on-Rails

def download_pdf @post= @post.avatar.service_url

send_data(

    "#{Rails.root}/public/#{@post}",
    filename: "#{@post}",
    type: "image/*",
    disposition: 'inline', stream: 'true', buffer_size: '4096'
)

end

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDavid TuiteView Question on Stackoverflow
Solution 1 - Ruby on-RailsnzajtView Answer on Stackoverflow
Solution 2 - Ruby on-RailsJoshua PinterView Answer on Stackoverflow
Solution 3 - Ruby on-RailssumskyiView Answer on Stackoverflow
Solution 4 - Ruby on-RailsKenneth JohnView Answer on Stackoverflow
Solution 5 - Ruby on-RailsDavid MorrowView Answer on Stackoverflow
Solution 6 - Ruby on-RailsKevin CooperView Answer on Stackoverflow
Solution 7 - Ruby on-Railskishore gopalakrishnanView Answer on Stackoverflow