Using send_file to download a file from Amazon S3?
Ruby on-RailsRubyRuby on-Rails-3Amazon S3Ruby on-Rails-3.2Ruby 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:
attachment.rb
In your 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