Previously, I covered uploading to S3 from a Rails app using a presigned-url. This works just fine, but means the data flows from the visitors computer to your server before heading to S3. I’ve previously showed how to upload the file directly to S3, which requires a world-writable bucket.
There’s also a hybrid solution that has the server generate a presigned-url which the browser then uses to go direct to S3, allowing the data to bypass the server. Let’s look at two ways to do this.
First, as is always the case when we want the browser to send a AJAX request to a different server, we need to configure S3 to sent a CORS header, specifically Access-Control-Allow-Origin. On the bucket Properties tab select Permissions > Edit CORS Configuration and paste in:
1 2 3 4 5 6 7 8
Replacing http://example.com with the domain you are serving the form from (or simply with *). If you need to allow other methods, can be found at https://docs.aws.amazon.com/AmazonS3/latest/dev/cors.html#how-do-i-enable-cors
Now that CORS is taken care of, we can make the AJAX call. As before,
we need the
aws-sdk gem to sign requests. In your Gemfile:
A simple approach is to have the server render the presigned URL in a form. With this approach we needed to predefine the filename as we signed URL before we know anything about the file. A UUID makes a good filename, unique filename in this case.
1 2 3 4
(Outside of rails you would need to
require 'securerandom'. See the
previous post for details on S3 credentials.)
Our form looks like:
1 2 3 4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
The downside of generating the URL before the file is chosen in the browser is that we can’t know the file extension (i.e. .jpg). However, the image will be served from S3 with the right mime type, displaying correctly in the browser, and most browsers will add the extension if the image is saved to disk.
A fancier approach solves this issue. Instead of generating the presigned URL when the form is displayed, we generate it via an API call after the form is submitted. First, we need a controller method that will take a file name and signed it:
1 2 3 4 5 6
Of course that requires some sort of checking to make sure we’re not overwriting an existing file, which is left as an exercise for the reader. An alternative is to extract the file extension and combine it with a UUID as above:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
There you have it, signed, direct to S3 uploads that avoid moving the upload through your server. Next time we’ll look at working with both uploads and data in S3.