Ruby on Rails AWS S3 Download URL - ruby-on-rails

How can I form a url link for a user so that when the user clicks on the link, it forces them to download the AWS S3 object?
I've seen these two solutions: Using send_file to download a file from Amazon S3? and Using send_file to download a file from Amazon S3? however, they seem to reference an old AWS S3 v1 SDK and there does not seem to be a url_for in the v2 AWS S3 SDK.
Thanks.

Ended up using the following code snippet to solve. Hope this helps others.
presigner = Aws::S3::Presigner.new
url = presigner.presigned_url(:get_object, #method
bucket: ENV['S3_BUCKET'], #name of the bucket
key: s3_key, #key name
expires_in: 7.days.to_i, #time should be in seconds
response_content_disposition: "attachment; filename=\"#{filename}\""
).to_s

Here's what I got:
def user_download_url(s3_filename, download_filename=nil)
s3_filename = s3_filename.to_s # converts pathnames to string
download_filename ||= s3_filename.split('/').last
url_options = {
expires_in: 60.minutes,
response_content_disposition: "attachment; filename=\"#{download_filename}\""
}
object = bucket.object(s3_filename)
object.exists? ? object.presigned_url(:get, url_options).to_s : nil
end
def bucket
#bucket ||= Aws::S3::Resource.new(region: ENV['AWS_REGION']).bucket(ENV['AWS_S3_BUCKET'])
end
To create a link for downloading, simply put redirect_to user_download_url(s3_file_path) in a controller action, and create a link to that controller action.

Related

Rails send_data streaming PDF as bytes, unless route accessed directly

I've got a simple Rails app that downloads a PDF from a (private) S3 bucket and serves it to the browser.
# app/controllers/file_controller.rb
class FileController < ApplicationController
def send_pdf
s3_file = Aws::S3::Resource.new(
region: "us-east-1",
access_key_id: S3_ACCESS_KEY_ID,
secret_access_key: S3_SECRET_ACCESS_KEY
).bucket('bucket-name').objects({prefix: "file_name"}).first.get.body.string
send_data s3_file, filename: "FileName.pdf", type: 'application/pdf', disposition: 'inline'
end
end
# config/routes.rb
Rails.application.routes.draw do
get 'file', to: 'file#send_pdf', defaults: { format: 'pdf' }
end
When accessing the route directly via URL, it displays the PDF fine.
When opening a link to the route in a new tab, it displays the PDF fine.
When opening a link in the same tab, the PDF data streams as text to the browser instead.
The same behavior occurs in Rails 4 and 5.
I'm probably missing something annoyingly minor here, but how can I get the open in the same tab behavior to properly display the PDF instead of streaming bytes as text?
Update 1:
Chrome gives a Failed to load PDF document error when send_pdf is modified to use send_file instead of send_data. (This error happens regardless of link click or direct route request.)
def send_pdf
# S3 download
temp_file = "#{Rails.root}/tmp/file.pdf"
File.open(temp_file,"wb") do |f|
f.write(s3_file)
f.close
end
send_file temp_file, filename: "FileName.pdf", type: 'application/pdf', disposition: 'inline'
File.delete(temp_file)
end
Have you tried send_file instead of send_data?
http://apidock.com/rails/v4.2.1/ActionController/DataStreaming/send_file

Rails download file direct from S3 with content-disposition = attachment?

This is my controller
Cotroller
def download
data = open(#attachment.file.url).read
#attachment.clicks = #attachment.clicks.to_i + 1
#attachment.save
send_data data, :type => #attachment.content_type, :filename => #attachment.name
end
example:
#attachment.file.url = "http://my_bucket.cloudfront.net/uploads/attachment/file/50/huge_file.pptx"
I did this, but if #attachement is a huge file (eg. 300MB), my server crash.
I want to allow users to download the file in the browser directly from my AWS server?
2) tip: Do you suggest to download file from S3 (where they are stored) or with CloudFront?
If you using carrierwave gem, you can try this to track number of clicks
def download
#attachment.clicks.to_i += 1
#attachment.save
redirect_to #attachment.file.url(query: {"response-content-disposition" => "attachment;"})
end
references:
Rails carrierwave S3 get url with Content-Disposition header

How to upload using CarrierWave an http_basic_authentication protected file

I'm trying to upload a file that is protected under http_basic_authentication with CarrierWave. Here is the tested code:
hs = House.new
hs.remote_house_url = "http://username:password#127.0.0.1:3000/houses/export.csv"
hs.save!
I'm expecting the file to be uploaded, but I get the following:
(13.2ms) BEGIN
(0.8ms) ROLLBACK
ActiveRecord::RecordInvalid: The validation failed : House could not download file: userinfo not supported. [RFC3986]
from /Users/htaidirt/.rvm/gems/ruby-2.1.1/gems/activerecord-4.0.0/lib/active_record/validations.rb:57:in `save!'
I know it's a problem with giving http_basic_authentication credentials (username & password) thanks to the message http_basic_authentication. But what is the right way to do it? Thanks.
I just encountered a similar problem. OpenURI does not allow you to supply basic auth credentials as part of the url, instead it should be like
open("http://www.your-website.net",
http_basic_authentication: ["user", "password"])
(which I found here: http://blog.andreamostosi.name/2013/04/open-uri-and-basic-authentication/)
Carrierwave does not seem to support this by default. For now, I have monkey patched the CarrierWave::Uploader::Download::RemoteFile class to add the required basic auth. I will try and submit a better version of this as a pull request so hopefully it can be added to the gem, but for now I created config/initializers/overrides.rb with the contents:
#add basic auth to carrierwave
module CarrierWave
module Uploader
module Download
class RemoteFile
private
def file
if #file.blank?
#file = Kernel.open(#uri.to_s, http_basic_authentication: ["USERNAME", "PASSWORD])
#file = #file.is_a?(String) ? StringIO.new(#file) : #file
end
#file
rescue Exception => e
raise CarrierWave::DownloadError, "could not download file: #{e.message}"
end
end
end
end
end

Convert and store to S3 with REST API / InkFilepicker

I have a Rails app on heroku. From the server side (using the REST API of InkFilepicker), I would like to convert a file, save it to my S3 bucket and store the S3 url to my model.
Concretely: Given an image (https://www.filepicker.io/api/file/hFHUCB3iTxyMzseuWOgG) I want to convert it (https://www.filepicker.io/api/file/hFHUCB3iTxyMzseuWOgG/convert?w=200&h=150&fit=clip) and store the converted image to my S3 bucket.
EDIT
Here is what I did at the end:
after_save :save_thumbnail_url_to_s3
def save_thumbnail_url_to_s3
convert_options = {
fit: 'clip',
h:500,
w:500
}
file = open("#{self.url}/convert?#{convert_options.to_query}")
# Writing file into S3 bucket
amazon = AWS::S3.new(access_key_id: ENV['AWS_ACCESS_KEY_ID'], secret_access_key: ENV['AWS_SECRET_ACCESS_KEY'])
bucket = amazon.buckets[ENV['AWS_BUCKET']]
object = bucket.objects[s3_media_path]
written_file = object.write(file, acl: :public_read) # :authenticated_read
self.update_column :thumbnail_url, written_file.public_url.to_s
end
If you are using the filepicker.io API you can convert your file with the API and then provide then use open-uri as below to create a file stream that can be sent to S3, Tempfile as below behaves like the File API in ruby
[3] pry(main)> require 'open-uri'
=> true
[4] pry(main)> file = open("https://www.filepicker.io/api/file/hFHUCB3iTxyMzseuWOgG/convert?...")
=> #
[5] pry(main)> file.class
=> Tempfile
You can simply use the aws-s3 gem : https://github.com/marcel/aws-s3
But be careful, Heroku is read only oriented, you will only be able to work on temp files.

Carrierwave & Amazon S3 file downloading/uploading

I have a rails 3 app with an UploadsUploader and a Resource model on which this is mounted. I recently switched to using s3 storage and this has broken my ability to download files using the send_to method. I can enable downloading using the redirect_to method which is just forwarding the user to an authenticated s3 url. I need to authenticate file downloads and I want the url to be http://mydomainname.com/the_file_path or http://mydomainname.com/controller_action_name/id_of_resource so I am assuming I need to use send_to, but is there a way of doing that using the redirect_to method? My current code follows. Resources_controller.rb
def download
resource = Resource.find(params[:id])
if resource.shared_items.find_by_shared_with_id(current_user) or resource.user_id == current_user.id
filename = resource.upload_identifier
send_file "#{Rails.root}/my_bucket_name_here/uploads/#{filename}"
else
flash[:notice] = "You don't have permission to access this file."
redirect_to resources_path
end
end
carrierwave.rb initializer:
CarrierWave.configure do |config|
config.fog_credentials = {
:provider => 'AWS', # required
:aws_access_key_id => 'xxxx', # copied off the aws site
:aws_secret_access_key => 'xxxx', #
}
config.fog_directory = 'my_bucket_name_here' # required
config.fog_host = 'https://localhost:3000' # optional, defaults to nil
config.fog_public = false # optional, defaults to true
config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {}
end
upload_uploader.rb
class UploadUploader < CarrierWave::Uploader::Base
storage :fog
def store_dir
"uploads"
end
end
All of this throws the error:
Cannot read file
/home/tom/Documents/ruby/rails/circlshare/My_bucket_name_here/uploads/Picture0024.jpg
I have tried reading up about carrierwave, fog, send_to and all of that but everything I have tried hasn't been fruitful as yet. Uploading is working fine and I can see the files in s3 bucket. Using re_direct would be great as the file wouldn't pass through my server. Any help appreciated. Thanks.
Looks like you want to upload to S3, but have not-public URLs. Instead of downloading the file from S3 and using send_file, you can redirect the user to the S3 authenticated URL. This URL will expire and only be valid for a little while (for the user to download).
Check out this thread: http://groups.google.com/group/carrierwave/browse_thread/thread/2f727c77864ac923
Since you're already setting fog_public to false, do you get an authenticated (i.e. signed) url when calling resource.upload_url

Resources