Since there are new regions of Amazon S3 services that accepts only Signature v4 (e.g. frankfurt) you cannot use aws-sdk-v1
gem's #presigned_url functionality. And newer aws-sdk
gem (beeing stil in preview for 2.x) doesn't have a functionality to do direct uploading either. Here's my inplementation of that functionality, build on top of aws-sdk v2.0.16.pre
and jquery (it's using some javascript stuff that older browsers won't support though).
For some basic information see this Heroku's Direct to S3 Image Uploads in Rails article https://devcenter.heroku.com/articles/direct-to-s3-image-uploads-in-rails . A "how to" by Amazaon itself in Authenticating Requests in Browser-Based Uploads Using POST (AWS Signature Version 4) http://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-UsingHTTPPOST.html (notice beelow are subsections of details, like how to create signing policy).
So, I build it the way, that only things that server needs to know after files has been uploaded to s3, are that in what 'folder' it was saved and the count of them (given that their keys are 0000, 0001 ... 9999 complementing with the folder the full path in the bucket). In this example I wanted from user additionaly only a caption of the image gallery that's being uploaded. This is the form needed:
<div class="new_gallery">
<input type="file" name="files_input" id="files_input" multiple="multiple">
<form id="save_gallery_form" action="/images/add_photo_gallery" accept-charset="UTF-8" method="post">
<input type="hidden" name="images_count" id="images_count">
<input type="hidden" name="folder" id="folder">
<input type="text" name="label" id="label" placeholder="caption of gallery">
</form>
<a id="button_upload" href="#">Start uploading</a>
<br>
<div id="files_list">
<!-- this is what each file will produce after selecting something -->
<!--<div class="file_field"><span class="title">IMG_4780.JPG</span><span class="key">0000</span></div>-->
<!--<div class="file_field"><span class="title">IMG_4781.JPG</span><span class="key">0001</span></div>-->
</div>
</div>
also add this js on the page: (fit these events to your needs)
$(function(){
var post_data = <%= Aws::FotocubeS3.post_data_for_images.to_json.html_safe %>; // a rails .erb way to hand over that data
DirectToS3uploader(post_data, {
start: function () {
writeStatus('Uploading files ...');
},
upload_fail: function () {
writeStatus('Uploading failed!');
},
to_save: function (data) {
writeStatus('Saving gallery ...');
$('#folder').val(data['folder']);
$('#images_count').val(data['files'].length);
$('#save_gallery_form').submit();
},
preconditions: function () {
if ($('#label').val()) return true;
writeStatus('Caption of gallery must be filled.');
}
});
});
I'll skip that javascript part, there's nothing important to explain that the previous example did not covered. It's just using one AJAX call and displays very simple progress bar per file, while POST-ing it to your S3 bucket.
The most important thing is the policy, that AWS requires. The DirectPostHelper
class will generate the basics for you using #generate_policy
(producing a DirectPostHelper::PostPolicy
instance). You can specify duration of this policy - if no argument given, it will set up expiration time for 2.hours
. You only need add your specific restrictions; follow above mentioned Amazon docs to check out what there is to restrict. Afterwards the method #generate_params
on your DirectPostHelper
instance produces form params that are essential to make a valid POST request to S3.
Now, Given that MY_PROJECT_BUCKET
is a Aws::S3::Bucket
class that is given to DirectPostHelper
's constructor, this is how I create policy in the example - the custom Aws::MyProjectS3#post_data_for_images
method:
post_helper = DirectPostHelper.new MY_PROJECT_BUCKET
# base of the key under which the file will be saved in S3 - aka folder in this case
folder = "images/#{Time.zone.now.utc.strftime '%Y%m%d%H%M%S'}/"
# additional custom params sent to S3
# 'acl' => 'public-read' means everybody can see that image
custom_params = {'content-type' => 'image/jpeg', 'acl' => 'public-read'}
policy = post_helper.generate_policy # hand over expiration time (by default it's 2.hours)
# the policy must include every additional parameter (here using exact match)
custom_params.each_pair{|k, v| policy.exact_condition k, v}
# this is how to set that the policy acceps only key that begins with given folder
policy.fit_in_condition 'starts-with', '$key', folder
# this is how to set up the range of image size
policy.fit_in_condition 'content-length-range', 1, 10485760
{ # this is what is returned and what my DirectToS3uploader javascript needs
url: bucket.url,
folder: folder,
params: post_helper.generate_params(policy, custom_params)
}