Skip to content
Joel Andritsch edited this page Apr 9, 2014 · 4 revisions

In order to make use of this uploader, you will need a server-side application that can handle generating the proper AWS S3 signature for each type of request. The AWS multipart upload API has several different operations you can use, however this uploader only relies on the following:

  • [POST] Initiate Multipart Upload
  • [PUT] Upload a single part
  • [GET] List uploaded parts
  • [POST] Complete Upload

The process for uploading a file to S3 is as follows:

  • Step 1: Get an "upload init" signature from the user-defined backend

  • Step 2: Using the init signature, call to S3 API to initiate a multipart upload request. If successful, an UploadId is returned.

  • Step 3: Using the UploadId, get the remaining signatures from the user-defined backend in a single call. The signatures returned include all chunk/part signatures, the "list parts" signature, and the "complete upload" signature.

  • Step 4: Start uploading each chunk to S3, using the appropriate signature for each chunk.

  • Step 5: Once all chunk uploads have completed, check to make sure that S3 has all the correct chunks that it was sent.

  • Step 6: Call the S3 API to complete the upload using the "complete" signature once all chunks have been verified.

For more information on the different types of operations AWS exposes, check out this link and this link too.

  • Note: At this time, BasicS3Uploader only supports v2 signtures, but will eventually be upgraded to use v4.

Your signature backend

As stated before, you need to provide your own signature API for this uploader. The main reason for this is security; you do not want to expose your AWS secret access key to the front end (which is used for part of the signing process). To work around this limitation, a server-side application needs to fill that position.

BasicS3Uploader will request each signature it needs at the right time, however you need to handle the response to these requests. The uploader expects the response type to be in JSON format.

There are only two actions that you'll need to implement:

  • One to handle returning the "init" signature. The response should look something like this:

      { 
        "signature": "some signature",
        "date": "some date"
      }
    
  • And one more to handle returning the remaining signatures (list, each chunk, and complete). The response should look something like this:

      // Note that each key in "chunk_signatures" refers to a single part/chunk number (1-based).
      {
        chunk_signatures: {
          1: { signature: "signature", date: "date" },
          2: { signature: "signature", date: "date" },
          3: { signature: "signature", date: "date" },
        },
        complete_signature: { signature: "signature", date: "date" },
        list_signature: { signature: "signature", date: "date" }
      }
    

How you define these endpoints is up to you, but BasicS3Uploader will, by default, attempt to get this information from the following URLs:

  • /get_init_signature
  • /get_remaining_signatures

You can configure this data through the signatureBackend, initSignaturePath, and remainingSignaturesPath settings. Check out the configuration page for more information on this.

How to generate signatures

Generating the actual signatures isn't too complicated, but will require a couple pieces of data. When BasicS3Uploader sends a request to retrieve a signature from your API, it will send along the following information:

  • bucket: The name of the S3 bucket
  • upload_id: The upload id. This is only used in the "remaining signatures" call.
  • key: The upload key chosen for the file.
  • chunk: The specific chunk/part number. This is only used in the "remaining signatures" call.
  • total_chunks: The number of chunks that will be uploaded. This is only used in the "remaining signatures" call.
  • mime_type: The type of the file.
  • acl: The Access Control List.
  • encrypted: Whether or not the upload should be encrypted.

Using this information, you can generate the proper signature by the following sample logic (Ruby):

class SignatureGenerator

  AWS_SECRET_KEY = "YOUR_SECRET_KEY"

  require 'base64'
  require 'digest'

  def initialize(params)
    @bucket     = params[:bucket]
    @date       = Time.now.strftime("%a, %d %b %Y %X %Z") # the date format that AWS requires
    @upload_id  = params[:upload_id]
    @key        = params[:key]
    @chunk      = params[:chunk]
    @mime_type  = params[:mime_type]
    @acl        = params[:acl]
    @encrypted  = params[:encrypted]
  end


  def encrypted_upload?
    @encrypted == "true"
  end

  def init_signature
    if encrypted_upload?
      encode("POST\n\n\n\nx-amz-acl:#{@acl}\nx-amz-date:#{@date}\nx-amz-server-side-encryption:AES256\n/#{@bucket}/#{@key}?uploads")
    else
      encode("POST\n\n\n\nx-amz-acl:#{@acl}\nx-amz-date:#{@date}\n/#{@bucket}/#{@key}?uploads")
    end
  end

  def part_signature
    encode("PUT\n\n#{@mime_type}\n\nx-amz-date:#{@date}\n/#{@bucket}/#{@key}?partNumber=#{@chunk}&uploadId=#{@upload_id}")
  end

  def list_signature
    encode("GET\n\n\n\nx-amz-date:#{@date}\n/#{@bucket}/#{@key}?uploadId=#{@upload_id}")
  end

  def complete_signature
    encode("POST\n\n#{@mime_type}\n\nx-amz-date:#{@date}\n/#{@bucket}/#{@key}?uploadId=#{@upload_id}")
  end

  def encode(data)
    sha1      = OpenSSL::Digest::Digest.new('sha1')
    hmac      = OpenSSL::HMAC.digest(sha1, AWS_SECRET_KEY, data)
    Base64.encode64(hmac).gsub("\n", "")
  end

end

Check out the sample app provided to see an example of how make use of this logic in an API/controller.

Clone this wiki locally