Here I show how to upload files directly to Amazon S3 using CarrierWave Direct and Fog. I then walk through another project which uses jQuery File Upload to handle multiple files and does not use CarrierWave.
Probably because the carrierwave_directgem (and Amazon S3 itself), doesn't support uploading multiple files in a single POST request. You need some kind of client-side scripting to separate the files into individual POST requests and insert the other form fields (key, policy, signature etc.) on the fly each time.
The final example still uses Carrierwave though, just not for the upload. The jQuery plugin uploads the file straight to S3. Then, in the done callback, the URL of the uploaded file gets "calculated" and POST-ed to the paintings#create action. The URL comes back in the response header from S3 (as "location" I think), so you could probably get it from there if you wanted.
In the paintings#create action, things start to look normal again. Either in the app, or a worker process, the record gets created and Carrierwave copies the raw file from the S3 bucket, to the location where your Carrierwave settings want it to be, then runs any image processing you have set up.
BTW, having a separate bucket for holding the raw uploads from the jQuery plugin is probably a good idea if you're not persisting the record before letting the user upload. You can end up with files that never get associated to anything in the database.
Let Carrierwave copy each file that gets associated to a record and process it. Carrierwave won't delete the raw upload file, but you can use the aws-sdk gem to delete it when Carrierwave finishes working. Finally, set up a task to periodically clear out old files in the "raw upload bucket" and that should take care of cleanup.
For that last part, you might want to prepend a timestamp to the SecureRandom portion of the key, then use the prefix method in aws-sdk, so you can do something like raw_uploads_bucket.objects_with_prefix('20120926').delete_all, to delete all the raw uploads from yesterday. Might be a good idea to check that all those workers are finished running too.
If it works for your UX though, persisting the record before letting the user upload makes cleanup a bit simpler.
+1 on vickash explanation, the path you give seems pretty adapted to anyone who already has a running CarrierWave+Fog based app and wants to extend it to take benefit of CORS without too much refactoring.
Thanks a lot to both you and Ryan :).
Great screencast. I made an example app that uses http://blueimp.github.com/jQuery-File-Upload/ to upload straight to S3. It was using a rather complex iframe trick to get around Amazons same origin policy. That was before Amazon introduced CORS, now my app is basically obsolete... ;-)
Ryan, will you make a gem from the multifile uploader you showed in the end? I think that'd be a great idea.
Just pushed an update that fixes IE8/9. For those that what to implement it as a fix to what Ryan provides its a rather small change to the helper method, see what I changed here:
Great cast Ryan! I used FOG recently via the Ruby Backup gem - to keep DB backups on S3, as well as a few different mirrors for user uploads. Great gem! Thanks again for the cast.
Great episode thx a lot.
I noticed something strange though. you create thumbnails based on the presence of the key attribute in the Painting class on the after_save
ruby
defenqueue_imageImageWorker.perform_async(id, key) if key.present?
end
but then you also set the key attribute in the perform method of the background task
ruby
defperform(id, key)
painting = Painting.find(id)
painting.key = key #setting the key again
painting.remote_image_url = painting.image.direct_fog_url(with_path:true)
painting.save! # enqueue_image will get called again
painting.update_column(:image_processed, true)
end
I think this will result in putting another message in the background queue. which will always keep your worker busy recreating thumbnails. Am I correct?
I played around with this issue, and the only solution that worked for me was adding an explicit flag to skip the processing:
ruby
defenqueue_imageImageWorker.perform_async(id, key) if key.present? && !skip_image_processing
enddefperform(id, key)
painting = Painting.find(id)
painting.key = key #setting the key again
painting.remote_image_url = painting.image.direct_fog_url(with_path:true)
painting.skip_image_processing = true
painting.save! # enqueue_image will get called again
painting.update_column(:image_processed, true)
end
Not sure if this is possible. The CORS configuration on S3 would have to allow origins from every single user that is using your API. The examples in this screencast upload files directly from a user's browser straight to S3. The file never goes through the rails application, only metadata about the file does.
You could get fancy and create some sort of registration process, where by the user of the API during the registration process enters the domain (origin) they plan to upload from. Or maybe they have a admin screen where they can change it out. AWS does expose an API where you can change settings on your S3 service. So when your users are in the admin screen, and they change the domain, then your rails app uses the S3 API to change the origin (or add) that the user entered. I hope I explained that well. I'm just spitballing here, never done this myself.
Nice episode Ryan, one thing, instead of using key.present? you should use: has_image_upload? since calling Painting.new.key will return uploads/<guid>/${filename} resulting in true when you call present?. It's documented in the README here: https://github.com/dwilkie/carrierwave_direct#uploading-from-a-remote-location
Ryan very timely as always. I was having trouble getting this to work in a legacy project. Same code worked great in a new rails app. But the legacy always resulted in "AccessDenied" response from AWS. Had me perplexed. Hours of tracing and debugging later, realized someone had set the time_zone in our rails config to EDT, and this threw off the expiration key in the policy_data that is sent in the POST request. Hope this might save others hours of tracing.
I had the same error, but my problem was not the redirect, but rather my Amazon S3 bucket parameter was incorrect, i.e., ENV["AWS_S3_BUCKET"] was not set to the correct value.
There is a missing step here, and in the example code.
When uploading images to S3, you want to specify the Content-Type, otherwise S3 will just default it to "application/octet-stream", which can cause your browser to throw warnings when you retrieve the file.
This caught me out, because Content-Type is listed in the multi-part form request as a component of the "file" input, however crucially S3 will NOT look at that. You need to pass it again.
In my case, using jqueryFileUpload as Ryan did, I have
Anyway if you want to process thumbnails using an EC2 instance, you can setup your own processing server using imgr or thumbd.
If you think setting up & maintaining a server purely for image processing is too much of a hassle, I suggest using external services like Blitline or thumbr.it. I've got a demo of Rails + Blitline working if you're interested.
I use the JQuery file upload (but just allowing one file to be selected). If the file is just part of a object, say user. So the user can upload a single picture to the user object. I don't want to create a new record when a file is uploaded, simply update a field. How does one accomplish that?
What is the "key" attribute? It's neither a column in Paintings nor attr_accessor but we still can access it with Painting.first.key in the console. Pretty weird to me.
I had a "gotcha" with the S3Uploader class because my rails application always has a time zone set other than UTC and so my s3 request was failing with an invalid expiration time policy.
I just had to add the .utc to the end of this line in the S3Uploader intialize to get it working...
Excellent episode.I am able to upload single file with carrierwave.
But I got 403(Forbidden) error when I try to upload multiple files using jquery file upload.
When I try to upload single/multiple file I get alert box saying failed to upload and in console it says "Failed to load resource: the server responded with a status of 403 (Forbidden)" and "XMLHttpRequest cannot load https://xyz.s3.amazonaws.com/. Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin."
No matter what I do I get the XMLHttpRequest cannot load https://3r_photos.s3.amazonaws.com/. Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin.
error. If I tweak the env vars to be wrong, I still get the same error. I have CORS set as indicated. Not sure how to proceed. Any ideas on how to debug this? I've tried deleting and recreating a bucket. Next up is deleting my AWS account I think.
Noob question for the jQuery File Upload setup:
Should i be checking that the image url sent from AWS S3 after user upload is actually from my S3 bucket and not from somewhere else harmful?
How come when running the example code, I got the error can't convert nil into String. I am I already added the aws_access_key and aws_secret_key and bucket as well
I use the gem s3_direct_upload inspirated by the code, but I have a problem.
Upload and progress bar works perfectly, my problem is :
The url of the uploaded file is a part a a Medium I create. The Medium contain "name", "description", "file_url" etc.
I don't display the S3 form at the creation of my medium, after saving, I redirect_to the Edit action and then display the S3 form to add a file. I tried to set post:media_url(@medium) or post:medium_edit_path(@medium) but it doesn't work cos I guess update require PUT and not POST.
So, is it possible to implement PUT with the gem or may I do something different ?
I am using the source code for "Gallery After" folder. When I upload Image , the Images are getting uploaded to S3 bucket. But while displaying all the uploaded images, I am getting "Processing..." rather than the uploaded image.
What could have gone wrong ?
i needed to use 2 different classes to get this to work in my project. this was a good explanation but wouldn't work for me since I was already using carrierwave on a live site and had images uploaded to specific folders already on S3. What I did was created an uploader class ImageUploader using the CarrierWave_Direct gem like in this example, and then kept my original class but renamed it to ImageProcessor. This way the original uploaded file is saved in a different folder in S3 and then I don't have to change the folder structure for current photos on my site. you can check out my solution here if interested:
great episode and I've got it (except I used carrierwave in the background to download from S3 and process it)
One small thing though: if the images have the same filename, (like if uploaded via iphone's mobile safari'), somehow the files end up being duplicates, instead of showing 2 different files. Has anyone had that issue?
I have downloaded the source code for the custom s3_uploader_form which uploads straight to s3.
Straight out of the box I can't upload anything to S3 as it always renders an alert stating "upload failed" for everything, however there is nothing in the trace to suggest why. Removing the paintings.js.coffee code and adding a submit_tag to the form enables me to upload files to s3, however this doesn't create a rails object because the js handles that. (I have another app running with carrierwave so I know these credentials, secret, access key etc are all correct)
My question is why are no files being uploaded when paintings.js.coffee is handling the file upload process?
But am a little unsure what to make of it. Was wondering if anyone else has come across this issue as it is pretty cool feature jquery upload. I am uploading video files and so being able to do it in batches means that their is less chance of file failure.
Great episode. Can you explain what motivated you to build the final example without fog or carrierwave?
Probably because the
carrierwave_directgem (and Amazon S3 itself), doesn't support uploading multiple files in a single POST request. You need some kind of client-side scripting to separate the files into individual POST requests and insert the other form fields (key, policy, signature etc.) on the fly each time.The final example still uses
Carrierwavethough, just not for the upload. The jQuery plugin uploads the file straight to S3. Then, in thedonecallback, the URL of the uploaded file gets "calculated" and POST-ed to thepaintings#createaction. The URL comes back in the response header from S3 (as "location" I think), so you could probably get it from there if you wanted.In the
paintings#createaction, things start to look normal again. Either in the app, or a worker process, the record gets created andCarrierwavecopies the raw file from the S3 bucket, to the location where yourCarrierwavesettings want it to be, then runs any image processing you have set up.BTW, having a separate bucket for holding the raw uploads from the jQuery plugin is probably a good idea if you're not persisting the record before letting the user upload. You can end up with files that never get associated to anything in the database.
Let
Carrierwavecopy each file that gets associated to a record and process it.Carrierwavewon't delete the raw upload file, but you can use theaws-sdkgem to delete it whenCarrierwavefinishes working. Finally, set up a task to periodically clear out old files in the "raw upload bucket" and that should take care of cleanup.For that last part, you might want to prepend a timestamp to the
SecureRandomportion of the key, then use theprefixmethod inaws-sdk, so you can do something likeraw_uploads_bucket.objects_with_prefix('20120926').delete_all, to delete all the raw uploads from yesterday. Might be a good idea to check that all those workers are finished running too.If it works for your UX though, persisting the record before letting the user upload makes cleanup a bit simpler.
+1 on vickash explanation, the path you give seems pretty adapted to anyone who already has a running CarrierWave+Fog based app and wants to extend it to take benefit of CORS without too much refactoring.
Thanks a lot to both you and Ryan :).
I've implemented something like this. I'm curious if anyone has had any luck getting it to work on IE8/9?
Great episode.
Can you please add in the show notes some of the libraries to process images client-side. It would be helpful! :)
Looks like the jquery file upload plugin has the ability to process images client-side:
https://github.com/blueimp/jQuery-File-Upload/wiki/Upload-multiple-resolutions-of-one-image-with-multiple-resize-options
Yes, but as usual, it's not 100% cross browser compatible. It is supported by Chrome, current versions of Firefox and the newest version of Safari (see https://github.com/blueimp/jQuery-File-Upload/wiki/Browser-support at the bottom of the page).
Great screencast. I made an example app that uses http://blueimp.github.com/jQuery-File-Upload/ to upload straight to S3. It was using a rather complex iframe trick to get around Amazons same origin policy. That was before Amazon introduced CORS, now my app is basically obsolete... ;-)
Ryan, will you make a gem from the multifile uploader you showed in the end? I think that'd be a great idea.
+1
+1
+1!
+1
+1
I created a simple gem that extracts out the form helper and cofeescript found in Ryan's rails app he mentioned at the end. You can find it here:
https://github.com/BlissOfBeing/s3_direct_upload
There aren't many tests for it yet, but hey it works for now. Would be grateful for any pull requests ;)
The url has changed:
https://github.com/WayneHoover/s3_direct_upload
Hey Wayne, very helpful. Do you know if your gem IE8/9 compatible?
Got it working on IE8/9 ?
Working on IE8/9 support...
Just pushed an update that fixes IE8/9. For those that what to implement it as a fix to what Ryan provides its a rather small change to the helper method, see what I changed here:
https://github.com/waynehoover/s3_direct_upload/commit/b9d4e54e3a27780ec340afba8bf2df8d60e14990
+1
+1
Great cast Ryan! I used FOG recently via the Ruby Backup gem - to keep DB backups on S3, as well as a few different mirrors for user uploads. Great gem! Thanks again for the cast.
Great episode thx a lot.
I noticed something strange though. you create thumbnails based on the presence of the key attribute in the Painting class on the after_save
but then you also set the key attribute in the perform method of the background task
I think this will result in putting another message in the background queue. which will always keep your worker busy recreating thumbnails. Am I correct?
I notice the same issue. Endless queue that kills my database performance.
What's the better way to avoid this?
I played around with this issue, and the only solution that worked for me was adding an explicit flag to skip the processing:
Any other, more elegant ideas?
I just tested this app and can confirm your assumption. It kept on recreating the thumbnails.
How would you go about allowing file uploads to S3 from an API (e.g. I want to allow users of my site to upload images to my service through our API)?
I'd do an upload via fog but we're using Heroku and the connection times out after 30 seconds.
Not sure if this is possible. The CORS configuration on S3 would have to allow origins from every single user that is using your API. The examples in this screencast upload files directly from a user's browser straight to S3. The file never goes through the rails application, only metadata about the file does.
You could get fancy and create some sort of registration process, where by the user of the API during the registration process enters the domain (origin) they plan to upload from. Or maybe they have a admin screen where they can change it out. AWS does expose an API where you can change settings on your S3 service. So when your users are in the admin screen, and they change the domain, then your rails app uses the S3 API to change the origin (or add) that the user entered. I hope I explained that well. I'm just spitballing here, never done this myself.
Nice episode Ryan, one thing, instead of using
key.present?you should use:has_image_upload?since callingPainting.new.keywill returnuploads/<guid>/${filename}resulting in true when you callpresent?. It's documented in the README here: https://github.com/dwilkie/carrierwave_direct#uploading-from-a-remote-locationRyan very timely as always. I was having trouble getting this to work in a legacy project. Same code worked great in a new rails app. But the legacy always resulted in "AccessDenied" response from AWS. Had me perplexed. Hours of tracing and debugging later, realized someone had set the time_zone in our rails config to EDT, and this threw off the expiration key in the policy_data that is sent in the POST request. Hope this might save others hours of tracing.
I got weird error. Have any of you experienced it?
InvalidPolicyDocumentInvalid Policy: Invalid JSON.06F63A9785323ECD+kEOXcGMeFmCmcwLDhJKI2xiUViqDMYjbrxwu9+b20mMG+ApNip7yEE0QNxjNuxK
I was getting the same and resolved it by including a redirect on success in the controller, as in the screencast.
@uploader.success_action_redirect = user_profile_url
I had the same error, but my problem was not the redirect, but rather my Amazon S3 bucket parameter was incorrect, i.e., ENV["AWS_S3_BUCKET"] was not set to the correct value.
There is a missing step here, and in the example code.
When uploading images to S3, you want to specify the Content-Type, otherwise S3 will just default it to "application/octet-stream", which can cause your browser to throw warnings when you retrieve the file.
This caught me out, because Content-Type is listed in the multi-part form request as a component of the "file" input, however crucially S3 will NOT look at that. You need to pass it again.
In my case, using jqueryFileUpload as Ryan did, I have
and
add: (e,data) ... data.form.find('#content-type').val(file.type) data.submit()Forgive a newbie. In ryans examples where do you put that code?
app/assets/javascripts/paintings.js.coffeeI noticed that
remote_image_urldownloads the image from the remote server[S3] and thesavemethod re-upload the images.The implication of that:
If the server downloads the image and re-uploads it again, We did not spare anything.
Is there a way to upload just the process image (like the thumbnails) and not re-upload the source image?
You can setup EC2 instance or stick with carrierwave?
Ryan ... come on man ... how??, that's the question!!!
greetings from Palestine <3
Manners man.
Anyway if you want to process thumbnails using an EC2 instance, you can setup your own processing server using imgr or thumbd.
If you think setting up & maintaining a server purely for image processing is too much of a hassle, I suggest using external services like Blitline or thumbr.it. I've got a demo of Rails + Blitline working if you're interested.
I use the JQuery file upload (but just allowing one file to be selected). If the file is just part of a object, say user. So the user can upload a single picture to the user object. I don't want to create a new record when a file is uploaded, simply update a field. How does one accomplish that?
HI ryan, I am opening up your code straight from source file, how come am getting
"can't convert nil into String" ?
what do I need to do to change this?
thanks much
I had that issue as well and you have to make sure aws_access_key_id , aws_secret_access_key, and bucket are not nil.
Can you add the Coffeescript to the show notes?
Anyboy got this working on IE?
What is the "key" attribute? It's neither a column in Paintings nor attr_accessor but we still can access it with Painting.first.key in the console. Pretty weird to me.
The carrierwave-direct gem implements the key methods through calling mount_uploader in the painting model.
I had a "gotcha" with the S3Uploader class because my rails application always has a time zone set other than UTC and so my s3 request was failing with an invalid expiration time policy.
I just had to add the .utc to the end of this line in the S3Uploader intialize to get it working...
thank you! i had the same problem!
Thank you! That was a very helpful comment. Ryan should add it to the ASCII cast too
Rails has a method
.httpdate, I think it's meant for this.Excellent episode Ryan. The community really appreciates your tireless efforts.
Excellent episode.I am able to upload single file with carrierwave.
But I got 403(Forbidden) error when I try to upload multiple files using jquery file upload.
When I try to upload single/multiple file I get alert box saying failed to upload and in console it says "Failed to load resource: the server responded with a status of 403 (Forbidden)" and "XMLHttpRequest cannot load https://xyz.s3.amazonaws.com/. Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin."
What do I need to do to fix this?
Thank you
It sounds like you don't have your CORS setup right (or at all). Check that you're using the right Origin Domain (as specified in the CORS policy).
Also you should use https://s3.amazonaws.com/xyz instead as you'll get invalid certs for https://xyz.s3.amazonaws.com.
I dare you to handle errors on direct uploads to S3. Then, the nightmare begins.
Hi,
just wondering if someone might be able to answer this?
1) how to prevent certain file type from being uploaded to AWS S3?
For example, i don't want user to upload video file at all, how to prevent it?
2) Is there a way in AWS S3 upload script to stop the files upload from triggering if each user is limit to 5 files upload per user?
Thanks for the help :- )
Hi,
Was wondering if CarrierWave and Fog be used to upload documents to box.com using a box API_KEY and and a box AUTH_TOKEN
Thanks
Hi,
I need files to be renamed before uploading
I get this to work :
But I need the extension at the end and this doesn't work:
Thanks for the help
No matter what I do I get the XMLHttpRequest cannot load https://3r_photos.s3.amazonaws.com/. Origin http://localhost:3000 is not allowed by Access-Control-Allow-Origin.
error. If I tweak the env vars to be wrong, I still get the same error. I have CORS set as indicated. Not sure how to proceed. Any ideas on how to debug this? I've tried deleting and recreating a bucket. Next up is deleting my AWS account I think.
Noob question for the jQuery File Upload setup:
Should i be checking that the image url sent from AWS S3 after user upload is actually from my S3 bucket and not from somewhere else harmful?
How come when running the example code, I got the error can't convert nil into String. I am I already added the aws_access_key and aws_secret_key and bucket as well
did you figure this out? Just got the same problem
I use the gem s3_direct_upload inspirated by the code, but I have a problem.
Upload and progress bar works perfectly, my problem is :
The url of the uploaded file is a part a a Medium I create. The Medium contain "name", "description", "file_url" etc.
I don't display the S3 form at the creation of my medium, after saving, I redirect_to the Edit action and then display the S3 form to add a file. I tried to set post:media_url(@medium) or post:medium_edit_path(@medium) but it doesn't work cos I guess update require PUT and not POST.
So, is it possible to implement PUT with the gem or may I do something different ?
Thanks for your help
Thanks Mark Wilson! that was my problem. Spent a day on it. I didn't see your comment yesterday! Thanks again
Anyone else having any issues similar to this after updating to 'jquery-rails 2.2.0'?
Uncaught Error: Syntax error, unrecognized expression: div class="upload"
img006.jpg
div class="progress"><div class="bar" style="width: 0%"...
i M HAVING SAME ISSUE
Have you found a solution to this problem ?
You need to substitute this line
with this other line in the initialize method of the paintings.js.cofee file
When I select the files or file, nothing happens.
I get the following error on page load:
TypeError: document.getElementById(...) is null
[Break On This Error]
return document.getElementById(id).innerHTML;
The id is: template-upload when I check it in Firefox.
Any thoughts?
Im having the same problem
Hi Ryan :
I am using the source code for "Gallery After" folder. When I upload Image , the Images are getting uploaded to S3 bucket. But while displaying all the uploaded images, I am getting "Processing..." rather than the uploaded image.
What could have gone wrong ?
i needed to use 2 different classes to get this to work in my project. this was a good explanation but wouldn't work for me since I was already using carrierwave on a live site and had images uploaded to specific folders already on S3. What I did was created an uploader class ImageUploader using the CarrierWave_Direct gem like in this example, and then kept my original class but renamed it to ImageProcessor. This way the original uploaded file is saved in a different folder in S3 and then I don't have to change the folder structure for current photos on my site. you can check out my solution here if interested:
http://stackoverflow.com/questions/14791655/carrierwave-direct-gem-keep-current-directory-structure-on-s3/14842832#14842832
In your ASCIIcast path is "/config/carrierwave.rb", but definitely it should be "/config/initializers/carrierwave.rb"
Thanks for reporting this mistake, I have corrected the text in the ASCIIcast
great episode and I've got it (except I used carrierwave in the background to download from S3 and process it)
One small thing though: if the images have the same filename, (like if uploaded via iphone's mobile safari'), somehow the files end up being duplicates, instead of showing 2 different files. Has anyone had that issue?
When running the example code, I got the error:
I have already added the aws_access_key and aws_secret_key and bucket as well. Please help me with this issue, I don't know where is the problem.
Check your initializer.
Try changing carrierwave.rb in your initializer to:
end
Ryan is awesome but the complexity of the solution is huge and I hate it.
for the custom s3 uploader, how would i go about adding attributes to be added along with the created photo. such as user_id and photo_name
I have downloaded the source code for the custom s3_uploader_form which uploads straight to s3.
Straight out of the box I can't upload anything to S3 as it always renders an alert stating "upload failed" for everything, however there is nothing in the trace to suggest why. Removing the paintings.js.coffee code and adding a submit_tag to the form enables me to upload files to s3, however this doesn't create a rails object because the js handles that. (I have another app running with carrierwave so I know these credentials, secret, access key etc are all correct)
My question is why are no files being uploaded when paintings.js.coffee is handling the file upload process?
I have a stackoverflow question about this question as well. http://stackoverflow.com/questions/16291848/upload-straight-to-s3-failing-railscasts-pro-383
thanks again for a great episode Ryan!
File chunking.
maxChunkSize: 1000000
I am finding that this directive is working for me. However S3 is not merging the file back together at their end.
I am only getting the last chunked bit of the file appearing in the folder.
I have found this
http://aws.amazon.com/articles/1109?_encoding=UTF8&jiveRedirect=1#14
But am a little unsure what to make of it. Was wondering if anyone else has come across this issue as it is pretty cool feature jquery upload. I am uploading video files and so being able to do it in batches means that their is less chance of file failure.
Hope someone can shed some light.
Kind regards,
Luke
First sign in through GitHub to post a comment.