With CORS, you can securely upload files directly into an S3 bucket, directly from the browser, without using an intermediate proxy (e.g. your web server). This option is a huge boost for web applications that handle big files. The alternative is to upload the file to the web server, and then the web server to send it to S3.
In this article we will see
- How to configure a S3 bucket so that it can accept requests from our domain
- How to implement a web form that will post the file to the S3 bucket
- How to upload a file with Ajax for a richer user experience
1. CORS Configuration Manifest
In order for S3 to accept HTTP requests from your domain you need to create a CORS configuration manifest that will declare which domain is allowed to make requests and which actions (PUT, POST, GET, DELETE) will be accepted.
To create the manifest [1]:
- Choose the S3 option from your AWS Management Console
- On the bucket you wish to grant CORS access to, click the properties option
- Note the option, Edit CORS Configuration
<?xml version="1.0" encoding="UTF-8"?> <CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> <CORSRule> <AllowedOrigin>www.codingstill.com</AllowedOrigin> <AllowedMethod>PUT</AllowedMethod> <AllowedMethod>POST</AllowedMethod> <AllowedMethod>GET</AllowedMethod> <AllowedMethod>HEAD</AllowedMethod> <MaxAgeSeconds>3000</MaxAgeSeconds> <AllowedHeader>*</AllowedHeader> </CORSRule> </CORSConfiguration>
Keep in mind here that if you need to include the port number if your domain runs in a port other than 80, a common case when working with Visual Studio.
2. Web form that performs the upload
Having configured our S3 bucket, we can go on and create our web form that will upload the file directly to our bucket. Here we will need a AWS Access Key ID and a AWS Secret Key. These can be created from the AWS management console. Make sure that these credentials have write access to your bucket.
<%@ Page Title="" Language="C#" AutoEventWireup="true" CodeBehind="awsupload.aspx.cs" Inherits="CodingStillDemo.awsupload%> <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" lang="en-US"> <head> <title>S3 POST Form</title> <meta charset="utf-8" /> </head> <body> <form action="https://codingstillcom.s3.amazonaws.com/" method="post" enctype="multipart/form-data"> <input type="hidden" name="key" value="uploads/<%=FileName=%>"> <input type="hidden" name="AWSAccessKeyId" value="<%=AwsAccessKeyID=%>"> <input type="hidden" name="acl" value="private"> <input type="hidden" name="success_action_redirect" value="http://localhost/"> <input type="hidden" name="policy" value="<%=Policy=%>"> <input type="hidden" name="signature" value="<%=Signature=%>"> <input type="hidden" name="Content-Type" value="image/jpeg"> <!-- Include any additional input fields here --> File to upload to S3: <input name="file" type="file"> <br> <input type="submit" value="Upload File to S3"> </form> </body> </html>
This form is from [2] which also explains what every field in the form is. One important thing is the the “file” field must be the last one in the form, since S3 will ignore any values after the actual file.
The article also explains how you can create a policy and a signature that are required in the web form, but not in C# (which is probably the reason you are reading this post!).
Here you will see a JSON representation of a policy that we will use in the web form. You can set its expiration date (currently it set at 2015/01/01) and a few conditions that we will be matched by S3 in order to accept the POST.
{"expiration": "2015-01-01T00:00:00Z", "conditions": [ {"bucket": "codingstillcom"}, ["starts-with", "$key", "uploads/"], {"acl": "private"}, ["starts-with", "$Content-Type", ""], ["content-length-range", 0, 1048576] ] }
Here is the C# code that converts the JSON policy into a base64 encoded string and also the calculated signature based on your AWS secret key.
public string AwsAccessKeyID { get { return "MY_AWS_ACCESS_KEY_ID"; } } private string AwsSecretKey { get { return "MY_AWS_SECRET_KEY"; } } private string myPolicy { get { return "{\"expiration\": \"2015-01-01T00:00:00Z\"," + "\"conditions\": [" + "{\"bucket\": \"codingstillcom\"}," + "[\"starts-with\", \"$key\", \"uploads/\"]," + "{\"acl\": \"public-read\"}," + "[\"starts-with\", \"$Content-Type\", \"\"]," + "[\"content-length-range\", 0, 5368709120]" + "]" + "}"; } } public string Policy { get { return Convert.ToBase64String(Encoding.ASCII.GetBytes((myPolicy)); } } public string Signature { get { return GetS3Signature(myPolicy); } } public string GetS3Signature(string policyStr) { string b64Policy = Convert.ToBase64String(Encoding.ASCII.GetBytes(policyStr)); byte[] b64Key = Encoding.ASCII.GetBytes(AwsSecretKey); HMACSHA1 hmacSha1 = new HMACSHA1(b64Key); return Convert.ToBase64String(hmacSha1.ComputeHash(Encoding.ASCII.GetBytes(b64Policy))); }
The above code successfully posts a file in a S3 bucket. It is really simple, but the downside with this is that you cannot show the progress and if it is a really big file (e.g. video) the user will have to look at the page and wait until the upload completes.
3. CORS upload with Ajax
In this section we will see how we can make the POST to S3 with JavaScript. This approach will give us the option to upload multiple files and display progress to the user while the user can continue and work the page.
The JavaScript code below is based on the sample found in [1] and added some handlers for a better user experience.
//Getting the values from C# var access_key = '<%=AccessKey%>'; var policy = '<%=Policy%>'; var signature = '<%=Signature%>'; function uploadFailed(evt) { //message user that the upload failed } function uploadCanceled(evt) { //message user that the upload was canceled } function uploadProgress(evt) { if (evt.lengthComputable) { // upload progress is being calculated as a percentage and stored in a variable var percentComplete = Math.round(evt.loaded * 100 / evt.total); } } function uploadComplete(evt) { //message user that the upload was completed } // The 'file' parameter is one item from the 'files' array of an input[type="file"] element. function upload_file(file) { var fd = new FormData(); var key = "uploads/" + file.name; fd.append('key', key); fd.append('AWSAccessKeyId', access_key); fd.append('acl', 'private'); fd.append('policy', policy) fd.append('signature', signature); fd.append('Content-Type', file.type); fd.append("file", file); var xhr = new XMLHttpRequest(); xhr.upload.addEventListener("progress", uploadProgress, false); xhr.addEventListener("load", uploadComplete, false); xhr.addEventListener("error", uploadFailed, false); xhr.addEventListener("abort", uploadCanceled, false); xhr.open('POST', 'https://codingstillcom.s3.amazonaws.com/', true); //MUST BE LAST LINE BEFORE YOU SEND xhr.send(fd); }
While trying this code open FireBug and go to the “Network” tab. There you might see before the POST request that the browser executes an OPTIONS request. This request is executed so that the browser can check if the POST is valid and will be accepted by the server.
References:
Leave a Reply