Thursday, 26 July 2018

Examples of the Windows Azure Storage Services REST API

The examples in this post were updated in September to work with the current version of the Windows Azure Storage REST API.
In the Windows Azure MSDN Azure Forum there are occasional questions about the Windows Azure Storage Services REST API. I have occasionally responded to these with some code examples showing how to use the API. I thought it would be useful to provide some examples of using the REST API for tables, blobs and queues – if only so I don’t have to dredge up examples when people ask how to use it. This post is not intended to provide a complete description of the REST API.
The REST API is comprehensively documented (other than the lack of working examples). Since the REST API is the definitive way to address Windows Azure Storage Services I think people using the higher level Storage Client API should have a passing understanding of the REST API to the level of being able to understand the documentation. Understanding the REST API can provide a deeper understanding of why the Storage Client API behaves the way it does.

Fiddler

The Fiddler Web Debugging Proxy is an essential tool when developing using the REST (or Storage Client) API since it captures precisely what is sent over the wire to the Windows Azure Storage Services.

Authorization

Nearly every request to the Windows Azure Storage Services must be authenticated. The exception is access to blobs with public read access. The supported authentication schemes for blobs, queues and tables and these are described here. The requests must be accompanied by an Authorization header constructed by making a hash-based message authentication code using the SHA-256 hash.
The following is an example of performing the SHA-256 hash for the Authorization header:
public static String CreateAuthorizationHeader(String canonicalizedString) {
    String signature = String.Empty;

    using (HMACSHA256 hmacSha256 = new HMACSHA256( Convert.FromBase64String(storageAccountKey) )) {
        Byte[] dataToHmac = System.Text.Encoding.UTF8.GetBytes(canonicalizedString);
        signature = Convert.ToBase64String(hmacSha256.ComputeHash(dataToHmac));
    }

    String authorizationHeader = String.Format(
        CultureInfo.InvariantCulture,
        "{0} {1}:{2}",
        AzureStorageConstants.SharedKeyAuthorizationScheme,
        AzureStorageConstants.Account,
        signature
    );

    return authorizationHeader;
}
This method is used in all the examples in this post.
AzureStorageConstants is a helper class containing various constants. Key is a secret key for Windows Azure Storage Services account specified by Account. In the examples given here, SharedKeyAuthorizationScheme is SharedKey.
The trickiest part in using the REST API successfully is getting the correct string to sign. Fortunately, in the event of an authentication failure the Blob Service and Queue Service responds with the authorization string they used and this can be compared with the authorization string used in generating the Authorization header. This has greatly simplified the us of the REST API.

Table Service API

The Table Service API supports the following table-level operations:
The Table Service API supports the following entity-level operations:
These operations are implemented using the appropriate HTTP VERB:
  • DELETE – delete
  • GET – query
  • MERGE – merge
  • POST – insert
  • PUT – update
This section provides examples of the Insert Entity and Query Entities operations.

Insert Entity

The InsertEntity() method listed in this section inserts an entity with two String properties, Artist and Title, into a table. The entity is submitted as an ATOM entry in the body of a request POSTed to the Table Service. In this example, the ATOM entry is generated by the GetRequestContentInsertXml() method. The date must be in RFC 1123 format in the x-ms-date header supplied to the canonicalized resource used to create the Authorization string. Note that the storage service version is set to “2012-02-12″ which requires the DataServiceVersion and MaxDataServiceVersion to be set appropriately.
public void InsertEntity(String tableName, String artist, String title)
{
    String requestMethod = "POST";

    String urlPath = tableName;

    String storageServiceVersion = "2012-02-12";

    String dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
    String contentMD5 = String.Empty;
    String contentType = "application/atom+xml";
    String canonicalizedResource = String.Format("/{0}/{1}", AzureStorageConstants.Account, urlPath);
    String stringToSign = String.Format(
            "{0}\n{1}\n{2}\n{3}\n{4}",
            requestMethod,
            contentMD5,
            contentType,
            dateInRfc1123Format,
            canonicalizedResource);
    String authorizationHeader = Utility.CreateAuthorizationHeader(stringToSign);

    UTF8Encoding utf8Encoding = new UTF8Encoding();
    Byte[] content = utf8Encoding.GetBytes(GetRequestContentInsertXml(artist, title));

    Uri uri = new Uri(AzureStorageConstants.TableEndPoint + urlPath);
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.Accept = "application/atom+xml,application/xml";
    request.ContentLength = content.Length;
    request.ContentType = contentType;
    request.Method = requestMethod;
    request.Headers.Add("x-ms-date", dateInRfc1123Format);
    request.Headers.Add("x-ms-version", storageServiceVersion);
    request.Headers.Add("Authorization", authorizationHeader);
    request.Headers.Add("Accept-Charset", "UTF-8");

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

    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(content, 0, content.Length);
    }

    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
        Stream dataStream = response.GetResponseStream();
        using (StreamReader reader = new StreamReader(dataStream))
        {
            String responseFromServer = reader.ReadToEnd();
        }
    }
}


private String GetRequestContentInsertXml(String artist, String title)
{
    String defaultNameSpace = "http://www.w3.org/2005/Atom";
    String dataservicesNameSpace = "http://schemas.microsoft.com/ado/2007/08/dataservices";
    String metadataNameSpace = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata";

    XmlWriterSettings xmlWriterSettings = new XmlWriterSettings();
    xmlWriterSettings.OmitXmlDeclaration = false;
    xmlWriterSettings.Encoding = Encoding.UTF8;

    StringBuilder entry = new StringBuilder();
    using (XmlWriter xmlWriter = XmlWriter.Create(entry))
    {
        xmlWriter.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
        xmlWriter.WriteWhitespace("\n");
        xmlWriter.WriteStartElement("entry", defaultNameSpace);
        xmlWriter.WriteAttributeString("xmlns", "d", null, dataservicesNameSpace);
        xmlWriter.WriteAttributeString("xmlns", "m", null, metadataNameSpace);
        xmlWriter.WriteElementString("title", null);
        xmlWriter.WriteElementString("updated", String.Format("{0:o}", DateTime.UtcNow));
        xmlWriter.WriteStartElement("author");
        xmlWriter.WriteElementString("name", null);
        xmlWriter.WriteEndElement();
        xmlWriter.WriteElementString("id", null);
        xmlWriter.WriteStartElement("content");
        xmlWriter.WriteAttributeString("type", "application/xml");
        xmlWriter.WriteStartElement("properties", metadataNameSpace);
        xmlWriter.WriteElementString("PartitionKey", dataservicesNameSpace, artist);
        xmlWriter.WriteElementString("RowKey", dataservicesNameSpace, title);
        xmlWriter.WriteElementString("Artist", dataservicesNameSpace, artist);
        xmlWriter.WriteElementString("Title", dataservicesNameSpace, title + "\n" + title);
        xmlWriter.WriteEndElement();
        xmlWriter.WriteEndElement();
        xmlWriter.WriteEndElement();
        xmlWriter.Close();
    }
    String requestContent = entry.ToString();
    return requestContent;
}
This generates the following request (as captured by Fiddler):
POST https://STORAGE_ACCOUNT.table.core.windows.net/authors HTTP/1.1
Accept: application/atom+xml,application/xml
Content-Type: application/atom+xml
x-ms-date: Sun, 08 Sep 2013 06:31:12 GMT
x-ms-version: 2012-02-12
Authorization: SharedKey STORAGE_ACCOUNT:w7Uu4wHZx4fFwa2bsxd/TJVZZ1AqMPwxvW+pYtoWHd0=
Accept-Charset: UTF-8
DataServiceVersion: 2.0;NetFx
MaxDataServiceVersion: 2.0;NetFx
Host: STORAGE_ACCOUNT.table.core.windows.net
Content-Length: 514
Expect: 100-continue
Connection: Keep-Alive
The body of the request is:
<?xml version="1.0" encoding="UTF-8"?>
 <entry xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" xmlns="http://www.w3.org/2005/Atom"><title /><updated>2013-09-08T06:31:13.0503771Z</updated><author><name /></author><id /><content type="application/xml"><m:properties><d:PartitionKey>Beckett</d:PartitionKey><d:RowKey>Molloy</d:RowKey><d:Artist>Beckett</d:Artist>
 <d:Title>Molloy
 Molloy</d:Title></m:properties></content></entry>
The Table Service generates the following response:
HTTP/1.1 201 Created
Cache-Control: no-cache
Content-Type: application/atom+xml;charset=utf-8
ETag: W/"datetime'2013-09-08T07%3A19%3A07.2189243Z'"
Location: https://STORAGE_ACCOUNT.table.core.windows.net/authors(PartitionKey='Beckett',RowKey='Molloy')
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 3818433a-4d89-4344-bcf1-ec248cf24d97
x-ms-version: 2012-02-12
Date: Sun, 08 Sep 2013 07:19:07 GMT
Content-Length: 1108
The Table Service generates the following response body:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xml:base="https://STORAGE_ACCOUNT.table.core.windows.net/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:etag="W/"datetime'2013-09-08T07%3A19%3A07.2189243Z'"" xmlns="http://www.w3.org/2005/Atom">
  <id>https://STORAGE_ACCOUNT.table.core.windows.net/authors(PartitionKey='Beckett',RowKey='Molloy')</id>
  <title type="text"></title>
  <updated>2013-09-08T07:19:07Z</updated>
  <author>
    <name />
  </author>
  <link rel="edit" title="authors" href="authors(PartitionKey='Beckett',RowKey='Molloy')" />
  <category term="STORAGE_ACCOUNT.authors" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <content type="application/xml">
    <m:properties>
      <d:PartitionKey>Beckett</d:PartitionKey>
      <d:RowKey>Molloy</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime">2013-09-08T07:19:07.2189243Z</d:Timestamp>
      <d:Artist>Beckett</d:Artist>
      <d:Title>Molloy
Molloy</d:Title>
    </m:properties>
  </content>
</entry>
Note that I should have URLEncoded the PartitionKey and RowKey but did not do so for simplicity. There are, in fact, some issues with the URL encoding of spaces and other symbols.

Get Entity

The GetEntity() method described in this section retrieves the single entity inserted in the previous section. The particular entity to be retrieved is identified directly in the URL.
public void GetEntity(String tableName, String partitionKey, String rowKey)
{
    String requestMethod = "GET";

    String urlPath = String.Format("{0}(PartitionKey='{1}',RowKey='{2}')", tableName, partitionKey, rowKey);

    String storageServiceVersion = "2012-02-12";

    String dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
    String canonicalizedResource = String.Format("/{0}/{1}", AzureStorageConstants.Account, urlPath);
    String stringToSign = String.Format(
            "{0}\n\n\n{1}\n{2}",
            requestMethod,
            dateInRfc1123Format,
            canonicalizedResource);
    String authorizationHeader = Utility.CreateAuthorizationHeader(stringToSign);

    Uri uri = new Uri(AzureStorageConstants.TableEndPoint + urlPath);
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = requestMethod;
    request.Headers.Add("x-ms-date", dateInRfc1123Format);
    request.Headers.Add("x-ms-version", storageServiceVersion);
    request.Headers.Add("Authorization", authorizationHeader);
    request.Headers.Add("Accept-Charset", "UTF-8");
    request.Accept = "application/atom+xml,application/xml";

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

    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
        Stream dataStream = response.GetResponseStream();
        using (StreamReader reader = new StreamReader(dataStream))
        {
            String responseFromServer = reader.ReadToEnd();
        }
    }
}
This generates the following request (as captured by Fiddler):
GET https://STORAGE_ACCOUNT.table.core.windows.net/authors(PartitionKey='Beckett',RowKey='Molloy') HTTP/1.1
x-ms-date: Sun, 08 Sep 2013 06:31:14 GMT
x-ms-version: 2012-02-12
Authorization: SharedKey STORAGE_ACCOUNT:1hWbr4aNq4JWCpNJY3rsLH1SkIyeFTJflbqyKMPQ1Gk=
Accept-Charset: UTF-8
Accept: application/atom+xml,application/xml
DataServiceVersion: 2.0;NetFx
MaxDataServiceVersion: 2.0;NetFx
Host: STORAGE_ACCOUNT.table.core.windows.net
The Table Service generates the following response:
HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: application/atom+xml;charset=utf-8
ETag: W/"datetime'2013-09-08T06%3A31%3A14.1579056Z'"
Server: Windows-Azure-Table/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: f4bd4c77-6fb6-42a8-8dff-81ea8d28fa2e
x-ms-version: 2012-02-12
Date: Sun, 08 Sep 2013 06:31:15 GMT
Content-Length: 1108
The returned entities, in this case a single entity, are returned in ATOM entry format in the response body:
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<entry xml:base="https://STORAGE_ACCOUNT.table.core.windows.net/" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata" m:etag="W/"datetime'2013-09-08T06%3A31%3A14.1579056Z'"" xmlns="http://www.w3.org/2005/Atom">
  <id>https://STORAGE_ACCOUNT.table.core.windows.net/authors(PartitionKey='Beckett',RowKey='Molloy')</id>
  <title type="text"></title>
  <updated>2013-09-08T06:31:15Z</updated>
  <author>
    <name />
  </author>
  <link rel="edit" title="authors" href="authors(PartitionKey='Beckett',RowKey='Molloy')" />
  <category term="STORAGE_ACCOUNT.authors" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
  <content type="application/xml">
    <m:properties>
      <d:PartitionKey>Beckett</d:PartitionKey>
      <d:RowKey>Molloy</d:RowKey>
      <d:Timestamp m:type="Edm.DateTime">2013-09-08T06:31:14.1579056Z</d:Timestamp>
      <d:Artist>Beckett</d:Artist>
      <d:Title>Molloy
Molloy</d:Title>
    </m:properties>
  </content>
</entry>

Blob Service API

The Blob Service API supports the following account-level operation:
The Blob Service API supports the following container-level operation:
The Blob Service API supports the following blob-level operation:
The Blob Service API supports the following operations on block blobs:
The Blob Service API supports the following operations on page blobs:
This section provides examples of the Put Blob and Lease Blob operations.

Put Blob

The Blob Service and Queue Service use a different form of shared-key authentication from the Table Service so care should be taken in creating the string to be signed for authorization. The blob type, BlockBlob or PageBlob, must be specified as a request header and consequently appears in the authorization string.
public void PutBlob(String containerName, String blobName)
{
    String requestMethod = "PUT";

    String urlPath = String.Format("{0}/{1}", containerName, blobName);

    String storageServiceVersion = "2012-02-12";

    String dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);

    String content = "Andrew Carnegie was born in Dunfermline";
    UTF8Encoding utf8Encoding = new UTF8Encoding();
    Byte[] blobContent = utf8Encoding.GetBytes(content);
    Int32 blobLength = blobContent.Length;

    const String blobType = "BlockBlob";

    String canonicalizedHeaders = String.Format(
            "x-ms-blob-type:{0}\nx-ms-date:{1}\nx-ms-version:{2}",
            blobType,
            dateInRfc1123Format,
            storageServiceVersion);
    String canonicalizedResource = String.Format("/{0}/{1}", AzureStorageConstants.Account, urlPath);
    String stringToSign = String.Format(
            "{0}\n\n\n{1}\n\n\n\n\n\n\n\n\n{2}\n{3}",
            requestMethod,
            blobLength,
            canonicalizedHeaders,
            canonicalizedResource);
    String authorizationHeader = Utility.CreateAuthorizationHeader(stringToSign);

    Uri uri = new Uri(AzureStorageConstants.BlobEndPoint + urlPath);
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = requestMethod;
    request.Headers.Add("x-ms-blob-type", blobType);
    request.Headers.Add("x-ms-date", dateInRfc1123Format);
    request.Headers.Add("x-ms-version", storageServiceVersion);
    request.Headers.Add("Authorization", authorizationHeader);
    request.ContentLength = blobLength;

    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(blobContent, 0, blobLength);
    }

    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
        String ETag = response.Headers["ETag"];
    }
}
This generates the following request:
PUT https://STORAGE_ACCOUNT.blob.core.windows.net/fife/dunfermline HTTP/1.1
x-ms-blob-type: BlockBlob
x-ms-date: Sun, 08 Sep 2013 06:28:29 GMT
x-ms-version: 2012-02-12
Authorization: SharedKey STORAGE_ACCOUNT:ntvh/lamVmikvwHhy6vRVBIh87kibkPlEOiHyLDia6g=
Host: STORAGE_ACCOUNT.blob.core.windows.net
Content-Length: 39
Expect: 100-continue
Connection: Keep-Alive
The body of the request is:
   Andrew Carnegie was born in Dunfermline
The Blob Service generates the following response:
HTTP/1.1 201 Created
Transfer-Encoding: chunked
Content-MD5: RYJnWGXLyt94l5jG82LjBw==
Last-Modified: Sun, 08 Sep 2013 06:28:31 GMT
ETag: "0x8D07A73C5704A86"
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: b74ef0a2-294d-4581-b8f1-6cda724bbdbf
x-ms-version: 2012-02-12
Date: Sun, 08 Sep 2013 06:28:30 GMT

Lease Blob

The Blob Service allows a user to lease a blob for a minute at a time and so acquire a write lock on it. The use case for this is the locking of a page blob used to store the VHD backing an writeable Azure Drive.
The LeaseBlob() example in this section demonstrates a subtle issue with the creation of authorization strings. The URL has a query string, comp=lease. Rather than using this directly in creating the authorization string it must be converted into comp:lease with a colon replacing the equal symbol – see modifiedURL in the example. Furthermore, the Lease Blob operation requires the use of an x-ms-lease-action to indicate whether the lease is being acquired, renewed, released or broken.
public void LeaseBlob(String containerName, String blobName)
{
    String requestMethod = "PUT";

    String urlPath = String.Format("{0}/{1}?comp=lease", containerName, blobName);
    String modifiedUrlPath = String.Format("{0}/{1}\ncomp:lease", containerName, blobName);

    const Int32 contentLength = 0;

    String storageServiceVersion = "2012-02-12";
    String dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
    String leaseAction = "acquire";
    String leaseDuration = "60";
    String canonicalizedHeaders = String.Format(
            "x-ms-date:{0}\nx-ms-lease-action:{1}\nx-ms-lease-duration:{2}\nx-ms-version:{3}",
            dateInRfc1123Format,
            leaseAction,
            leaseDuration,
            storageServiceVersion);
    String canonicalizedResource = String.Format("/{0}/{1}", AzureStorageConstants.Account, modifiedUrlPath);
    String stringToSign = String.Format(
            "{0}\n\n\n{1}\n\n\n\n\n\n\n\n\n{2}\n{3}",
            requestMethod,
            contentLength,
            canonicalizedHeaders,
            canonicalizedResource);
    String authorizationHeader = Utility.CreateAuthorizationHeader(stringToSign);

    Uri uri = new Uri(AzureStorageConstants.BlobEndPoint + urlPath);
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = requestMethod;
    request.Headers.Add("x-ms-date", dateInRfc1123Format);
    request.Headers.Add("x-ms-lease-action", leaseAction);
    request.Headers.Add("x-ms-lease-duration", leaseDuration);
    request.Headers.Add("x-ms-version", storageServiceVersion);
    request.Headers.Add("Authorization", authorizationHeader);
    request.ContentLength = contentLength;

    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
        String leaseId = response.Headers["x-ms-lease-id"];
    }
}
This generates the following request:
PUT https://STORAGE_ACCOUNT.blob.core.windows.net/fife/dunfermline?comp=lease HTTP/1.1
x-ms-date: Sun, 08 Sep 2013 06:28:31 GMT
x-ms-lease-action: acquire
x-ms-lease-duration: 60
x-ms-version: 2012-02-12
Authorization: SharedKey rebus:+SQ5+RFZg3hUaws5XCRHxsDgXb1ycdRIz5EKyHJWP7s=
Host: rebus.blob.core.windows.net
Content-Length: 0
The Blob Service generates the following response:
HTTP/1.1 201 Created
Server: Windows-Azure-Blob/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 4b6ff77f-f885-4f74-803a-c92920d225c3
x-ms-version: 2012-02-12
x-ms-lease-id: b1320c2c-65ad-41d6-a7bd-85a4242c0ac5
Date: Sun, 08 Sep 2013 06:28:31 GMT
Content-Length: 0

Queue Service API

The Queue Service API supports the following queue-level operation:
The Queue Service API supports the following queue-level operation:
The Queue Service API supports the following message-level operations:
This section provides examples of the Put Message and Get Message operations.

Put Message

The most obvious curiosity about Put Message is that it uses the HTTP verb POST rather than PUT. The issue is presumably the interaction of the English language and the HTTP standard which states that PUT should be idempotent and that the Put Message operation is clearly not since each invocation merely adds another message to the queue. Regardless, it did catch me out when I failed to read the documentation well enough – so take that as a warning.
The content of a message posted to the queue must be formatted in a specified XML schema and must then be UTF8 encoded.
public void PutMessage(String queueName, String message)
{
    String requestMethod = "POST";

    String urlPath = String.Format("{0}/messages", queueName);

    String storageServiceVersion = "2012-02-12";
    String dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);

    String messageText = String.Format(
            "<QueueMessage><MessageText>{0}</MessageText></QueueMessage>", message);
    UTF8Encoding utf8Encoding = new UTF8Encoding();
    Byte[] messageContent = utf8Encoding.GetBytes(messageText);
    Int32 messageLength = messageContent.Length;

    String canonicalizedHeaders = String.Format(
            "x-ms-date:{0}\nx-ms-version:{1}",
            dateInRfc1123Format,
            storageServiceVersion);
    String canonicalizedResource = String.Format("/{0}/{1}", AzureStorageConstants.Account, urlPath);
    String stringToSign = String.Format(
            "{0}\n\n\n{1}\n\n\n\n\n\n\n\n\n{2}\n{3}",
            requestMethod,
            messageLength,
            canonicalizedHeaders,
            canonicalizedResource);
    String authorizationHeader = Utility.CreateAuthorizationHeader(stringToSign);

    Uri uri = new Uri(AzureStorageConstants.QueueEndPoint + urlPath);
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = requestMethod;
    request.Headers.Add("x-ms-date", dateInRfc1123Format);
    request.Headers.Add("x-ms-version", storageServiceVersion);
    request.Headers.Add("Authorization", authorizationHeader);
    request.ContentLength = messageLength;

    using (Stream requestStream = request.GetRequestStream())
    {
        requestStream.Write(messageContent, 0, messageLength);
    }

    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
        String requestId = response.Headers["x-ms-request-id"];
    }
}
This generates the following request:
POST https://rebus.queue.core.windows.net/revolution/messages HTTP/1.1
x-ms-date: Sun, 08 Sep 2013 06:34:08 GMT
x-ms-version: 2012-02-12
Authorization: SharedKey rebus:nyASTVWifnxHKnj2wXwuzzzXz5CxUBZj58SToV5QFK8=
Host: rebus.queue.core.windows.net
Content-Length: 76
Expect: 100-continue
Connection: Keep-Alive
The body of the request is:
    <QueueMessage><MessageText>Saturday in the cafe</MessageText></QueueMessage>

The Queue Service generates the following response:
HTTP/1.1 201 Created
Server: Windows-Azure-Queue/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: 14c6e73b-15d9-480c-b251-c4c01b48e529
x-ms-version: 2012-02-12
Date: Sun, 08 Sep 2013 06:34:09 GMT
Content-Length: 0

Get Messages

The Get Messages operation described in this section retrieves a single message with the default message visibility timeout of 30 seconds.
public void GetMessage(String queueName)
{
    string requestMethod = "GET";

    String urlPath = String.Format("{0}/messages", queueName);

    String storageServiceVersion = "2012-02-12";
    String dateInRfc1123Format = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);
    String canonicalizedHeaders = String.Format(
            "x-ms-date:{0}\nx-ms-version:{1}",
            dateInRfc1123Format,
            storageServiceVersion);
    String canonicalizedResource = String.Format("/{0}/{1}", AzureStorageConstants.Account, urlPath);
    String stringToSign = String.Format(
            "{0}\n\n\n\n\n\n\n\n\n\n\n\n{1}\n{2}",
            requestMethod,
            canonicalizedHeaders,
            canonicalizedResource);
    String authorizationHeader = Utility.CreateAuthorizationHeader(stringToSign);

    Uri uri = new Uri(AzureStorageConstants.QueueEndPoint + urlPath);
    HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
    request.Method = requestMethod;
    request.Headers.Add("x-ms-date", dateInRfc1123Format);
    request.Headers.Add("x-ms-version", storageServiceVersion);
    request.Headers.Add("Authorization", authorizationHeader);
    request.Accept = "application/atom+xml,application/xml";

    using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
    {
        Stream dataStream = response.GetResponseStream();
        using (StreamReader reader = new StreamReader(dataStream))
        {
            String responseFromServer = reader.ReadToEnd();
        }
    }
}
This generates the following request:
GET https://rebus.queue.core.windows.net/revolution/messages HTTP/1.1
x-ms-date: Sun, 08 Sep 2013 06:34:11 GMT
x-ms-version: 2012-02-12
Authorization: SharedKey rebus:K67XooYhokw0i0AlCzYQ4GeLLrJih1r1vSqiO9DBo0c=
Accept: application/atom+xml,application/xml
Host: rebus.queue.core.windows.net
The Queue Service generates the following response:
HTTP/1.1 200 OK
Content-Type: application/xml
Server: Windows-Azure-Queue/1.0 Microsoft-HTTPAPI/2.0
x-ms-request-id: efb21a86-7d66-47fd-b13d-7aa74fce0568
x-ms-version: 2012-02-12
Date: Sun, 08 Sep 2013 06:34:12 GMT
Content-Length: 484
The message is returned in the response body as follows:
    <?xml version="1.0" encoding="utf-8"?><QueueMessagesList><QueueMessage><MessageId>05fd902f-6031-4ef4-8298-ef3844ec3bc6</MessageId><InsertionTime>Sun, 08 Sep 2013 06:34:11 GMT</InsertionTime><ExpirationTime>Sun, 15 Sep 2013 06:34:11 GMT</ExpirationTime><DequeueCount>1</DequeueCount><PopReceipt>AgAAAAMAAAAAAAAAAL+zgF2szgE=</PopReceipt><TimeNextVisible>Sun, 08 Sep 2013 06:34:43 GMT</TimeNextVisible><MessageText>Saturday in the cafe</MessageText></QueueMessage></QueueMessagesList>

I noticed that some newline specifiers in strings (\n) were lost when the blog was auto-ported from Windows Live Spaces to WordPress. I have put them back in but it is possible I missed some. Consequently, in the event of a problem you should check the newlines in canonicalizedHeaders and stringToSign.

No comments:

Post a Comment