Call Copy Blob (Azure REST API) from SQL Server

Following my recent post Call Azure REST API from SQL Server I've received many requests about how to call the new API Copy Blob. Its description is here: http://msdn.microsoft.com/en-us/library/windowsazure/dd894037.aspx. As before, let me show you what we want to achieve:

 

We want to have a simple stored procedure to call with a storage account, a source and a destination blob (we need the shared key too).

 EXEC Azure.CopyBlob '<storage>',
 '<source container>', '<source blob>',
  '<destination container>', '<destination blob>',
    '<sharedkey>';

What we should do is to call the correct REST entity we want to create using the PUT verb. We must specify as header the source blob: x-ms-copy-source:name (don't forget to include it in the signature otherwise you'll get a very unpolite 403 forbidden!). Here is the main procedure code:

 private static Dictionary<string, string> _CopyPageBlob(string storageAccount,
            string srcCcontainer, string srcBlobName,
            string destContainer, string destBlobName,
            string sharedKey)
        {
            string strCopySource = string.Format("https://{0:S}.blob.core.windows.net/{1:S}/{2:S}", storageAccount, srcCcontainer, srcBlobName);
            string strCopyDestination = string.Format("https://{0:S}.blob.core.windows.net/{1:S}/{2:S}", storageAccount, destContainer, destBlobName);

            string CanonicalizedResource = "/";
            CanonicalizedResource += storageAccount;
            CanonicalizedResource += "/" + destContainer + "/" + destBlobName;

            System.Net.HttpWebRequest Request = (System.Net.HttpWebRequest)System.Net.HttpWebRequest.Create(strCopyDestination);
            Request.Method = "PUT";
            Request.ContentLength = 0;

#region Add HTTP headers
            string strDate = DateTime.UtcNow.ToString("R");

            Request.Headers.Add("x-ms-copy-source", strCopySource);
            Request.Headers.Add("x-ms-date", strDate);
            Request.Headers.Add("x-ms-version", "2012-02-12");
#endregion

            string CanonicalizedHeader = "x-ms-copy-source:" + strCopySource + "\n";
            CanonicalizedHeader += "x-ms-date:" + strDate + "\n";
            CanonicalizedHeader += "x-ms-version:2012-02-12\n";

            string MessageSignature = Request.Method + "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += Request.ContentLength + "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";
            MessageSignature += "\n";

            MessageSignature += CanonicalizedHeader;
            MessageSignature += CanonicalizedResource;

            byte[] SignatureBytes = System.Text.Encoding.UTF8.GetBytes(MessageSignature);
            System.Security.Cryptography.HMACSHA256 SHA256 = new System.Security.Cryptography.HMACSHA256(Convert.FromBase64String(sharedKey));

            String AuthorizationHeader = "SharedKey " + storageAccount + ":" + Convert.ToBase64String(SHA256.ComputeHash(SignatureBytes));
            Request.Headers.Add("Authorization", AuthorizationHeader);

            using (System.Net.HttpWebResponse response = (System.Net.HttpWebResponse)Request.GetResponse())
            {
                if (response.StatusCode == System.Net.HttpStatusCode.Accepted)
                {
                    Dictionary<string, string> dRes = new Dictionary<string, string>();
                    for (int i = 0; i < response.Headers.Count; i++)
                        dRes.Add(response.Headers.GetKey(i), response.Headers.Get(i));
                    return dRes;
                }
                else
                    throw new Exception(response.ToString());
            }
        }

The wrapper SQL method is very simple then:

 [SqlProcedure]
        public static void CopyPageBlob(SqlString storageAccount,
            SqlString sourceCcontainer, SqlString sourceBlobName,
            SqlString destinationContainer, SqlString destinationBlobName,
            SqlString sharedKey)
        {
            Dictionary<string, string> dRes = _CopyPageBlob(
                storageAccount.ToString(),
                sourceCcontainer.ToString(), sourceBlobName.ToString(),
                destinationContainer.ToString(), destinationBlobName.ToString(),
                sharedKey.ToString());

            PushRecordset(dRes);
        }

That alone should enable you to use this fantastic Azure feature. One point is to note, though: you might get the result before the copy is finished. The x-ms-copy-status header will tell you that (either success of pending); that's why we push it back to the caller in a recordset.

 

Happy coding,

Francesco Cogno