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?
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"
I think the best way to handle this is using an expiring S3 url. The other methods have the following issues:
send_data
doesn't produce the expected "browser download".download
controller action.My implementation looks like this:
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
<%= 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.
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)
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.
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
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}", } }
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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.