Uploading to Amazon S3 with AJAX POST

Introduction

I’ve been on vacation back home in the States for the past few weeks. Things like Techn9ne concerts, public speaking and lots of sunshine have kept me away from my computer. Now that vacation is over, and I’ve become reacquainted with work I’ll put a new post together.

Not too long ago I wrote a post explaining how to generate an authorized POST policy with Ruby for document uploads to an Amazon S3 bucket from the browser. The authenticated policy generation is only the first step, though. In this post, I’ll walk you through actually deploying the document to the bucket using the POST policy information.

In this post, we will just build the frontend part for the previous post, linked above. The backend code from the previous post generates an AWS POST policy with an authenticated-read ACL.

The system I built like this for work is a lot more complex than we’ll get here (it was handling confidential files, etc), but it essentially does the same thing.

The Form

This is easy. Probably the easiest part. Any type of upload form will work, and we won’t spend too much time making things pretty for this tutorial. So, we’ll just use a simple HTML form:

  <h4>Our document upload form :)</h4>
  <input type="file" id="file" class="docUploadInput">
  <hr/>
  <button id="upload">Upload</button>

We are going to upload the file to AWS (after requesting the POST policy from our backend) via AJAX. So, I didn’t bother to write out all of the form markup. We could build a form, and listen for submission of that form. Instead, for simplicity, I’ll just add a listener for the click of our submission button.

The POST Policy

Now that we have our form, we need to ask for an upload policy. Typically, your backend will restrict the file type, filesize and probably more than a few attributes. Then, you will either be denied the upload, or you will be given an upload policy. When I build this system for work, we had a matrix of requirements that the files had to meet before they could be uploaded.

Listen for the button click

We need to know when the user is ready for our Javascript to takeover, so we’ll add a listener to the upload button above.

    $(document).ready({
      .on('click', '.uploadButton', function() {
          uploadDocument();
      })
    });

This jQuery just listens for the button click, then executes the uploadDocument method.

POST Policy

Before we can upload to AWS, we need to ask our backend for a POST policy. This provides us with authorization to upload this specific file to our bucket.

  function uploadDocument() {
      return getAmazonPolicy()
          .then(deployDocToAmazon)
          .then(updateBackend)
          .then(updateUI);
  }

The above Javascript simply chains a few methods that depend upon each other together. This way we know that we won’t upload the document to AWS until we have the policy.

In the getAmazonPolicy method, we will collect some details about the file the user wants to upload and ask the backend for an upload policy.

  function getAmazonPolicy() {

     prepareUI();

      var docs = $('.docUploadInput').prop('files');

      if(docs.length <= 0) {
          alert('Please select a document to upload.').removeClass('hidden');
          return false;
      }

      var request_payload = {
          document_name: docs[0].name,
          content_length: docs[0].size,
          content_type: docs[0].type
      };

      return $.ajax({
          type: 'POST',
          url: '/path/to/backend/script',
          dataType: 'json',
          data: request_payload,
          success: function(data) {
              if(data.error) {

                  alert('Error: '+data.error);
              } else {
                  return data;
              }
          },
          error: function(data) {
              updateUI();
              alert(data.status+': '+data.statusText+' while generating upload policy.');
              return false;
          }
      });
  }

This collects some information about the file the user selected, and makes a POST request to our backend script. The backend makes a decision based on the file information we have provided, and either generates and upload policy or declines. Typically, you’d also store the information about the file to be deploy in your DB somewhere.

Upload to AWS S3

Now that we have our upload policy (requested in the 2B), we can attempt to upload the file to the S3 bucket. All of the upload information (including the endpoint to use) is returned from our backend. (Again, see Generating AWS S3 POST Policy with Ruby for more details on that).

    function deployDocToAmazon(data) {

        var policy = data[0].upload_policy;
        var file = document.getElementById('file').files[0];
        var fd = new FormData();

        fd.append('key', policy.key);
        fd.append('acl', policy.acl);
        fd.append('success_action_status', policy.success_action_status);
        fd.append('Content-Type', file.type);
        fd.append('X-Amz-Credential', policy.x_amz_credential);
        fd.append('x-Amz-Algorithm', policy.x_amz_algorithm);
        fd.append('X-Amz-Date', policy.x_amz_date);
        fd.append('Policy', policy.policy);
        fd.append('X-Amz-Signature', policy.x_amz_signature);
        fd.append('X-Amz-Server-Side-Encryption', policy.x_amz_server_side_encryption);
        fd.append("file", file);

        return $.ajax({
            url: policy.upload_url,
            data: fd,
            processData: false,
            contentType: false,
            dataType: 'xml',
            type: 'POST',
            success: function(data) {
                return data;
            },
            error: function(data) {
                alert(data.status+': '+data.statusText);
            }
        });
    }

This function is incredibly simple. All we do is take the policy information returned from the backend and build the request. Notice the order of the headers I appended to my FormData object. This order is important. All of this information is what was used when we generated the authorization on the backend.

After we build the request, we simply submit the file data to the S3 bucket.

Conclusion

That’s it. It really is that simple. It was even this simple when I built this at work. The backend does all of the heavy lifting and decision making, leaving the frontend with the simple task of submitting the file data along with the policy.

Notice I didn’t add the updateBackend or updateUI methods in this tutorial. Typically, these will just be requests to your backend notifying you of the result from the deploy to Amazon. This lets you update your DB so your records are up to date.

If you use this tutorial for one of your projects, be sure to leave the link below. I’d like to check it out

Your email address will not be published. Required fields are marked *

*