2012-08-16 81 views
2

我們目前使用REST API(基於Microsoft示例)從.NET客戶端配置文件計算機上以塊爲單位上傳blob。 REST API示例直接使用Azure存儲帳戶名稱和訪問密鑰構建請求標頭中的SharedKey條目。對於生產代碼,我們需要在我們的服務器上計算SharedKey,並將其交付給客戶端以在會話期間使用。爲Blob上傳並使用Azure REST-API SharedKey

blob的SharedKey創建示例爲我提供了一個Url plus查詢字符串,其中包含訪問參數。

我的問題:如何將此Url /查詢字符串鍵格式與Azure REST API所需的SharedKey標題條目結合使用?

任何指針或提示非常感謝! R

回答

1

在這裏你去。顯然可以對此代碼進行很多改進:)試試看。請讓我知道它是否適合你。

 
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.IO; 
using System.Web; 
using System.Net; 
using System.Collections.Specialized; 
using System.Globalization;

namespace UploadBlobUsingSASUrl { class Program { //This is part of SAS signature (query string). We will construct the URI later using this. private static string sasSignature = "sr=c&st=2012-08-16T14%3A38%3A48Z&se=2012-08-16T15%3A38%3A48Z&sp=w&sig=aNTLYQtwA1UmjG7j8Lg44t8YThL16FkNYBi54kl4ZKo%3D"; //Blob storage endpoint private static string blobStorageEndpoint = "http://127.0.0.1:10000/devstoreaccount1"; //Blob container name private static string blobContainerName = "[blob container name. SAS URI with Write permission must be created on this blob container]"; //File to upload private static string fileToUpload = @"[Full path of the file you wish to upload]"; //This is the default block size (This application always assumes that a file will be split in blocks and then uploaded). private static int blockSize = 256 * 1024;//256 KB //Storage service version (Unless you're using latest SAS related changes in cloud storage, use this version). For development storage always use this version. private static string x_ms_version = "2011-08-18"; //Template for put block list private static string blockListTemplate = @"{0}"; // Template for block id (to be included in put block list template) private static string blockIdTemplate = "{0}"; //We'll keep a list of block ids. private static List blockIds = new List(); static void Main(string[] args) {

FileInfo file = new FileInfo(fileToUpload); long totalFileSize = file.Length;//Get the file size long bytesFrom = 0; long bytesRemaining = totalFileSize; string blobName = file.Name; //This is the base URI which will be used for put blocks and put block list operations. //It is essentially would be something like "http://127.0.0.1:10000/devstoreaccount1/myblobcontainer/myblobname?sassignature" string baseUri = string.Format("{0}/{1}/{2}?{3}", blobStorageEndpoint, blobContainerName, blobName, sasSignature); int counter = 0; //In this loop, we'll read file in chunks and try and upload one chunk at a time. while (true) { int bytesToRead = blockSize; if (bytesRemaining < blockSize) { bytesToRead = (int)bytesRemaining; } //Read the file in chunks byte[] fileContents = ReadFile(fileToUpload, bytesFrom, bytesToRead); bytesRemaining -= fileContents.Length; bytesFrom += fileContents.Length; //Create block id string blockId = string.Format("Block-{0:D5}", counter); //Append that to the block id list. blockIds.Add(blockId); //Now let's upload the block. var isBlockUploaded = UploadBlock(baseUri, fileContents, blockId); Console.WriteLine("Block Id: " + blockId + " Block Size: " + fileContents.Length + " Uploaded: " + isBlockUploaded); counter++; if (bytesRemaining <= 0) { break; } } //All blocks uploaded, now let's commit the block list var isBlockListCommitted = CommitBlockList(baseUri, blockIds); Console.WriteLine("Is Block List Committed: " + isBlockListCommitted); Console.WriteLine("Press any key to terminate the program ...."); Console.ReadLine(); } /// <summary> /// This function reads a chunk of the file and returns that as byte array. /// </summary> /// <param name="fileName"></param> /// <param name="bytesFrom"></param> /// <param name="bytesToRead"></param> /// <returns></returns> private static byte[] ReadFile(string fileName, long bytesFrom, int bytesToRead) { using (FileStream fs = new FileStream(fileName, FileMode.Open)) { byte[] byteArray = new byte[bytesToRead]; fs.Seek(bytesFrom, SeekOrigin.Begin); fs.Read(byteArray, 0, bytesToRead); return byteArray; } } /// <summary> /// This function uploads a block. /// </summary> /// <param name="baseUri"></param> /// <param name="blockContents"></param> /// <param name="blockId"></param> /// <returns></returns> private static bool UploadBlock(string baseUri, byte[] blockContents, string blockId) { bool isBlockUploaded = false; //Create request URI - string uploadBlockUri = string.Format("{0}&comp=block&blockId={1}", baseUri, Convert.ToBase64String(Encoding.UTF8.GetBytes(blockId))); // Create request object var request = (HttpWebRequest) HttpWebRequest.Create(uploadBlockUri); NameValueCollection requestHeaders = new NameValueCollection(); var requestDate = DateTime.UtcNow; //Add request headers. Please note that since we're using SAS URI, we don't really need "Authorization" header. requestHeaders.Add("x-ms-date", string.Format(CultureInfo.InvariantCulture, "{0:R}", requestDate)); requestHeaders.Add("x-ms-version", x_ms_version); request.Headers.Add(requestHeaders); //Set content length header. request.ContentLength = blockContents.Length; //Set request HTTP method. request.Method = "PUT"; // Send the request using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(blockContents, 0, blockContents.Length); } // Get the response using (var response = (HttpWebResponse)request.GetResponse()) { isBlockUploaded = response.StatusCode.Equals(HttpStatusCode.Created); } return isBlockUploaded; } /// <summary> /// This function commits the block list. /// </summary> /// <param name="baseUri"></param> /// <param name="blockIds"></param> /// <returns></returns> private static bool CommitBlockList(string baseUri, List<string> blockIds) { bool isBlockListCommitted = false; //Create the request payload StringBuilder blockIdsPayload = new StringBuilder(); foreach (var blockId in blockIds) { blockIdsPayload.AppendFormat(blockIdTemplate, Convert.ToBase64String(Encoding.UTF8.GetBytes(blockId))); } string putBlockListPayload = string.Format(blockListTemplate, blockIdsPayload.ToString()); // Create request URI string putBlockListUrl = string.Format("{0}&comp=blocklist", baseUri); // Create request object. var request = (HttpWebRequest)HttpWebRequest.Create(putBlockListUrl); NameValueCollection requestHeaders = new NameValueCollection(); //Add request headers. Please note that since we're using SAS URI, we don't really need "Authorization" header. var requestDate = DateTime.UtcNow; requestHeaders.Add("x-ms-date", string.Format(CultureInfo.InvariantCulture, "{0:R}", requestDate)); requestHeaders.Add("x-ms-version", x_ms_version); byte[] requestPayload = Encoding.UTF8.GetBytes(putBlockListPayload); //Set content length header. request.ContentLength = requestPayload.Length; request.Headers.Add(requestHeaders); //Set request HTTP method. request.Method = "PUT"; // Send the request using (Stream requestStream = request.GetRequestStream()) { requestStream.Write(requestPayload, 0, requestPayload.Length); } // Get the response using (var response = (HttpWebResponse)request.GetResponse()) { isBlockListCommitted = response.StatusCode.Equals(HttpStatusCode.Created); } return isBlockListCommitted; } }

}

+0

感謝Guarav!太好了,這看起來和我的分段上傳代碼很相似,只不過你確認我們可以離開SharedKey請求頭。我試過了,但收到了一個「不好的請求」的消息,所以我一定有其他的錯誤。另外,顯然PUT塊的大小不一定是關鍵calc的一部分,因爲它在REST API中。我會告訴你。無論如何,我欠你一個(並且欠下一個堆棧溢出)。 R – GGleGrand 2012-08-16 20:13:50

+0

您可以分享您的代碼以便查看嗎?我的猜測是,您要麼丟失了一個請求頭文件,要麼SAS代碼可能存在問題。 – 2012-08-17 05:12:59

+0

我會在完成後發佈代碼 - 或者如果我無法正常工作:-)再次感謝! [R – GGleGrand 2012-08-17 12:19:21

1

我相信你需要生成共享訪問簽名(SAS)URL。我對麼?當您生成SAS URL時,權限會在URI中進行編碼,因此您不必再使用授權密鑰。

爲了生成SAS,你會發現這兩個環節有用:

http://msdn.microsoft.com/en-us/library/windowsazure/hh508996

http://msdn.microsoft.com/en-us/library/windowsazure/ee395415

+0

感謝拉夫,我已經做了,它看起來像這樣: http://127.0.0.1:10000/devstoreaccount1/products-wawi-blob-uploads/5126d6a2-941d-4c17- a8x1-caf411cb13t2?st = 2012-08-16T08:40:07Z&se = 2012-08-16T09:20:07Z&sr = c&sp = rw&sig = 5CXKziA + xBxP5FT52N/OIIvuRWtSl0d/OPTpTHqPIiI = 並返回給我的客戶端。我的客戶端必須使用REST Api,它目前在請求頭中創建它自己的SharedKey條目。您是否說我不必在請求頭中添加此條目?當我閱讀REST API文檔時,似乎需要... – GGleGrand 2012-08-16 10:02:54

+0

我不認爲你可以在REST API中使用SAS URL。然而,你可以做的是使用簡單的WebRequest/WebResponse並使用SAS URI作爲你的請求URL。看看這篇博客文章,它展示瞭如何使用SAS URI上傳文件:http://wely-lau.net/2012/01/10/uploading-file-securely-to-windows-azure-blob-storage -with-shared-access-signature-via-rest-api/ – 2012-08-16 10:22:21

+0

感謝Gaurav,尼斯嘗試:-)但對我們不起作用,因爲我們正在使用PutBlock增量上傳大數據文件,而單個PUT將無法處理負載。而且,這兩者之間必須有直接的語義聯繫:我看到很多共同點,我認爲,我只是在與格式和語法作鬥爭。或者,也許我可以在服務器上爲Rest API生成SharedKey標題?問題在那裏:當前樣本impl。在「StringToSign」中使用Blob大小,如果我在服務器上生成共享訪問密鑰,則不知道Blob大小。任何人有想法嗎?謝謝! – GGleGrand 2012-08-16 13:03:25

0

這是我最終的代碼(減去一些清理辦)的成功在仿真環境中測試:我用下面的代碼能夠在發展中存儲上傳的斑點。首先,客戶端代碼,它們是來自Microsoft REST API示例BlobHelper.cs的改編方法。然後是提供客戶端代碼使用的端點URL的服務器端代碼。再次感謝提示! [R

// 
// Client side: The "Endpoint" used below is the Uri as returned from the Server-side code below. 
// 
// 
    public bool PutBlock(int blockId, string[] blockIds, byte[] value) 
    { 
     return Retry<bool>(delegate() 
     { 
      HttpWebResponse response; 

      try 
      { 
       SortedList<string, string> headers = new SortedList<string, string>(); 

       byte[] blockIdBytes = BitConverter.GetBytes(blockId); 
       string blockIdBase64 = Convert.ToBase64String(blockIdBytes); 

       blockIds[blockId] = blockIdBase64; 

       // SharedAccessKey version. 
       //End result will look similar to this in Fiddler if correct: 
       //PUT http://127.0.0.1:10000/devstoreaccount1/uploads/aecdfa39-7eaa-474a-9333-ecf43e6a0508?st=2012-08-17T16%3A11%3A53Z&se=2012-08-17T16%3A51%3A53Z&sr=b&sp=rw&sig=2%2Fs0R1L78S55pW5o2WontVvlZypjkTriWoljnycPbFc%3D&comp=block&blockid=AAAAAA== HTTP/1.1 
       // 
       response = CreateRESTRequestDirectUtf8("PUT", "&comp=block&blockid=" + blockIdBase64, value, headers).GetResponse() as HttpWebResponse; 

       response.Close(); 
       return true; 
      } 
      catch (WebException ex) 
      { 
       if (ex.Status == WebExceptionStatus.ProtocolError && 
        ex.Response != null && 
        (int)((HttpWebResponse)ex.Response).StatusCode == 409) 
        return false; 

       throw; 
      } 
     }); 
    } 

    ///<summary> 
    /// Put block list - complete creation of blob based on uploaded content. 
    /// </summary> 
    /// <param name="container">The container.</param> 
    /// <param name="blob">The BLOB.</param> 
    /// <param name="blockIds">The block ids.</param> 
    /// <returns></returns> 
    public bool PutBlockList(string[] blockIds) 
    { 
     return Retry<bool>(delegate() 
     { 
      HttpWebResponse response; 

      try 
      { 
       StringBuilder content = new StringBuilder(); 
       content.Append("<?xml version=\"1.0\" encoding=\"utf-8\"?>"); 
       content.Append("<BlockList>"); 
       for (int i = 0; i < blockIds.Length; i++) 
       { 
        content.Append("<Latest>" + blockIds[i] + "</Latest>"); 
       } 
       content.Append("</BlockList>"); 
       response = CreateRESTRequest("PUT", "&comp=blocklist", content.ToString(), null).GetResponse() as HttpWebResponse; 

       response.Close(); 
       return true; 
      } 
      catch (WebException ex) 
      { 
       if (ex.Status == WebExceptionStatus.ProtocolError && 
        ex.Response != null && 
        (int)(ex.Response as HttpWebResponse).StatusCode == 409) 
        return false; 

       throw; 
      } 
     }); 
    } 

    /// <summary> 
    /// Construct and issue a REST request and return the response. 
    /// </summary> 
    /// <param name="method">The method.</param> 
    /// <param name="resource">The resource.</param> 
    /// <param name="requestBody">The request body.</param> 
    /// <param name="headers">The headers.</param> 
    /// <param name="ifMatch">If match.</param> 
    /// <param name="md5">The MD5.</param> 
    /// <returns></returns> 
    public HttpWebRequest CreateRESTRequest(string method, string resource, string requestBody = null, SortedList<string, string> headers = null, 
     string ifMatch = "", string md5 = "") 
    { 
     byte[] byteArray = null; 
     DateTime now = DateTime.UtcNow; 
     Uri uri = new Uri(Endpoint + resource); 

     HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest; 
     request.Method = method; 
     request.ContentLength = 0; 
     request.Headers.Add("x-ms-date", now.ToString("R", System.Globalization.CultureInfo.InvariantCulture)); 
     request.Headers.Add("x-ms-version", "2009-09-19"); //2009-09-19, 2011-08-18 

     if (IsTableStorage) 
     { 
      request.ContentType = "application/atom+xml"; 

      request.Headers.Add("DataServiceVersion", "1.0;NetFx"); 
      request.Headers.Add("MaxDataServiceVersion", "1.0;NetFx"); 
     } 

     if (headers != null) 
     { 
      foreach (KeyValuePair<string, string> header in headers) 
      { 
       request.Headers.Add(header.Key, header.Value); 
      } 
     } 

     if (!String.IsNullOrEmpty(requestBody)) 
     { 
      request.Headers.Add("Accept-Charset", "UTF-8"); 

      byteArray = Encoding.UTF8.GetBytes(requestBody); 
      request.ContentLength = byteArray.Length; 
     } 

     // We now get our SharedAccessKey from the server 
     //request.Headers.Add("Authorization", AuthorizationHeader(method, now, request, ifMatch, md5)); 

     if (!String.IsNullOrEmpty(requestBody)) 
     { 
      request.GetRequestStream().Write(byteArray, 0, byteArray.Length); 
     } 
     return request; 
    } 

    /// <summary> 
    /// Creates the REST request direct UTF8. 
    /// </summary> 
    /// <param name="method">The method.</param> 
    /// <param name="resource">The resource.</param> 
    /// <param name="requestBodyUtf8">The request body UTF8.</param> 
    /// <param name="headers">The headers.</param> 
    /// <param name="ifMatch">If match.</param> 
    /// <param name="md5">The MD5.</param> 
    /// <returns></returns> 
    private HttpWebRequest CreateRESTRequestDirectUtf8(string method, string resource, byte[] requestBodyUtf8, SortedList<string, string> headers = null, string ifMatch = "", string md5 = "") 
    { 
     //byte[] byteArray = null; 
     DateTime now = DateTime.UtcNow; 
     Uri uri = new Uri(Endpoint + resource); 

     HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest; 
     request.Method = method; 
     request.ContentLength = 0; 
     request.Headers.Add("x-ms-date", now.ToString("R", System.Globalization.CultureInfo.InvariantCulture)); 
     request.Headers.Add("x-ms-version", "2009-09-19"); //2009-09-19, 2011-08-18 

     if (IsTableStorage) 
     { 
      request.ContentType = "application/atom+xml"; 

      request.Headers.Add("DataServiceVersion", "1.0;NetFx"); 
      request.Headers.Add("MaxDataServiceVersion", "1.0;NetFx"); 
     } 

     // Additional headers can be passed in as a formal parameter: 
     if (headers != null) 
     { 
      foreach (KeyValuePair<string, string> header in headers) 
      { 
       request.Headers.Add(header.Key, header.Value); 
      } 
     } 

     if (requestBodyUtf8 != null) 
     { 
      request.Headers.Add("Accept-Charset", "UTF-8"); 
      request.ContentLength = requestBodyUtf8.Length; 
     } 

     // We now get our SharedAccessKey from the server 
     //request.Headers.Add("Authorization", AuthorizationHeader(method, now, request, ifMatch, md5)); 

     if (requestBodyUtf8 != null) 
     { 
      request.GetRequestStream().Write(requestBodyUtf8, 0, requestBodyUtf8.Length); 
     } 

     return request; 
    } 

    // 
    // Server side: The returned Uri here is the "Endpoint" used in the client code. 
    // 
    /// <summary> 
    /// Gets the blob-level shared access signature for the named blob 
    /// </summary> 
    /// <param name="blobName">The unique blob name Guid.</param> 
    /// <returns>The fully qualified Azure Shared Access Signature Query String to be used in azure upload connections</returns> 
    public Uri GetBlobUploadUrl(Guid blobName) 
    { 
     string containerName = BlobContainerName; 
     const string permissions = "rw"; 
     string sharedAccessSignature = CreateSharedAccessSignature(containerName, blobName.ToString(), permissions); 

     string urlPath; 
     if (Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.IsEmulated) 
     { // Emulation environment 
      urlPath = String.Format("{0}/{1}/{2}{3}", _blobEndpoint, containerName, blobName, sharedAccessSignature);    
     } 
     else 
     { // Cloud 
      urlPath = String.Format("{0}{1}/{2}{3}", _blobEndpoint, containerName, blobName, sharedAccessSignature); 
     } 

     Uri uri = new Uri(urlPath); 

     return uri; 
    } 

    /// <summary> 
    /// Creates a blob-level shared access signature. 
    /// </summary> 
    /// <param name="containerName">The blob container name.</param> 
    /// <param name="blobName">The blob name, a unique ID which will be passed back to the client.</param> 
    /// <param name="permissions">String of access levels, "r" = read, "w" = write "rw" = both etc.</param> 
    /// <returns>The fully qualified Azure Shared Access Signature Query String</returns> 
    private string CreateSharedAccessSignature(string containerName, string blobName, string permissions) 
    { 
     // SAS without stored container policy 
     const string iso8061Format = "{0:yyyy-MM-ddTHH:mm:ssZ}"; 
     DateTime startTime = DateTime.UtcNow.AddMinutes(-10d); //UtcNow; 
     DateTime expiryTime = startTime.AddMinutes(40d); 
     string start = string.Format(iso8061Format, startTime); 
     string expiry = string.Format(iso8061Format, expiryTime); 
     string stringToSign = string.Format("{0}\n{1}\n{2}\n/{3}/{4}/{5}\n", permissions, start, expiry, _accountName, containerName, blobName); 

     // SAS with stored container policy 
     //string stringToSign = String.Format("\n\n\n/{0}/{1}\n{2}", accountName, containerName, policyId); 

     string rawSignature = String.Empty; 
     Byte[] keyBytes = Convert.FromBase64String(_accountKey); 
     using (HMACSHA256 hmacSha256 = new HMACSHA256(keyBytes)) 
     { 
      Byte[] utf8EncodedStringToSign = System.Text.Encoding.UTF8.GetBytes(stringToSign); 
      Byte[] signatureBytes = hmacSha256.ComputeHash(utf8EncodedStringToSign); 
      rawSignature = Convert.ToBase64String(signatureBytes); 
     } 

     string sharedAccessSignature = String.Format("?st={0}&se={1}&sr=b&sp={2}&sig={3}", Uri.EscapeDataString(start), Uri.EscapeDataString(expiry), permissions, Uri.EscapeDataString(rawSignature)); 
     // 
     // End result will look like this in Fiddler if correct: 
     //PUT http://127.0.0.1:10000/devstoreaccount1/uploads/aecdfa39-7eaa-474a-9333-ecf43e6a0508?st=2012-08-17T16%3A11%3A53Z&se=2012-08-17T16%3A51%3A53Z&sr=b&sp=rw&sig=2%2Fs0R1L78S55uW5o2WontVvrZypckTriWoijnyrPbFc%3D&comp=block&blockid=AAAAAA== HTTP/1.1 
     // 
     return sharedAccessSignature; 
    }