Статьи

Примеры API REST служб хранилища Windows Azure

Примеры в этом посте были обновлены в сентябре для работы с текущей версией Windows Azure Storage REST API.

На форуме Windows Azure MSDN Azure иногда возникают вопросы об API-интерфейсе REST служб хранилища Windows Azure . Я иногда отвечал на них некоторыми примерами кода, показывающими, как использовать API. Я подумал, что было бы полезно привести некоторые примеры использования REST API для таблиц, больших двоичных объектов и очередей — если только так, мне не нужно углубляться в примеры, когда люди спрашивают, как его использовать. Этот пост не предназначен для предоставления полного описания REST API.

API REST полностью документирован (за исключением отсутствия рабочих примеров). Поскольку REST API — это окончательный способ обращения к Windows Azure Storage Services, я думаю, что люди, использующие API-интерфейс клиента хранилища более высокого уровня, должны иметь представление о REST API до уровня, позволяющего понять документацию. Понимание REST API может дать более глубокое понимание того, почему API-интерфейс Storage Storage ведет себя так, как он работает.

обманщик

Fiddler Web Debugging Proxy является важным инструментом при разработке с использованием REST (или клиента Storage) API , поскольку он фиксирует именно то , что передается по проводам к Windows Azure Storage Services.

авторизация

Почти каждый запрос к Windows Azure Storage Services должен пройти проверку подлинности. Исключением является доступ к BLOB-объектам с открытым доступом для чтения. Поддерживаемые схемы аутентификации для больших двоичных объектов, очередей и таблиц, и они описаны здесь . Запросы должны сопровождаться заголовком авторизации, созданным путем создания кода аутентификации сообщения на основе хеша с использованием хеша SHA-256 .

Ниже приведен пример выполнения хэша SHA-256 для заголовка авторизации:

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;
}

Этот метод используется во всех примерах в этом посте.

AzureStorageConstants — это вспомогательный класс, содержащий различные константы. Ключ является секретным ключом для учетной записи Windows Azure Storage Services, указанной учетной записью . В приведенных здесь примерах SharedKeyAuthorizationScheme — это SharedKey .

Самым сложным в успешном использовании REST API является получение правильной строки для подписи. К счастью, в случае сбоя аутентификации служба Blob и служба очереди отвечают строкой авторизации, которую они использовали, и это можно сравнить со строкой авторизации, используемой при создании заголовка авторизации. Это значительно упростило использование API REST.

API Table Table

API Таблица Service поддерживает следующие операции на уровне таблиц:

API Таблица Service поддерживает следующие операции на уровне субъектов:

Эти операции реализованы с использованием соответствующего HTTP VERB:

  • УДАЛИТЬ — удалить
  • GET — запрос
  • MERGE — объединить
  • POST — вставить
  • PUT — обновить

В этом разделе приведены примеры операций вставки сущностей и запросов.

Вставить объект

Метод InsertEntity (), указанный в этом разделе, вставляет в таблицу сущность с двумя свойствами String, Artist и Title . Сущность представляется в виде записи ATOM в теле запроса, отправленного в табличную службу. В этом примере запись ATOM генерируется методом GetRequestContentInsertXml (). Дата должна быть в формате RFC 1123 в заголовке x-ms-date, предоставленном канонизированному ресурсу, используемому для создания строки авторизации. Обратите внимание, что версия службы хранения установлена ​​на «2012-02-12», что требует правильной установки DataServiceVersion и MaxDataServiceVersion .

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;
}

Это генерирует следующий запрос (как захвачено 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

Тело запроса:

<?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>

Служба таблиц генерирует следующий ответ:

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

Служба таблиц генерирует следующее тело ответа:

<?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>

Обратите внимание, что я должен был URLEncoded PartitionKey и RowKey, но не сделал этого для простоты. На самом деле есть некоторые проблемы с кодировкой URL пробелов и других символов.

Получить сущность

Метод GetEntity (), описанный в этом разделе, извлекает один объект, вставленный в предыдущем разделе. Конкретная сущность, которую нужно получить, идентифицируется непосредственно в 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();
        }
    }
}

Это генерирует следующий запрос (как захвачено 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

Служба таблиц генерирует следующий ответ:

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

Возвращенные объекты, в данном случае один объект, возвращаются в формате записи ATOM в теле ответа:

<?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>

API службы Blob

API Blob Service поддерживает следующие операции на уровне аккаунта:

API Blob Service поддерживает следующие операции контейнер уровня:

API Blob Service поддерживает следующие операции блоб уровня:

API Blob Service поддерживает следующие операции на блок сгустков:

API Blob Service поддерживает следующие операции на странице сгустков:

В этом разделе приведены примеры операций Put Blob и Lease Blob.

Положите Blob

Служба BLOB-объектов и служба очередей используют другую форму проверки подлинности с общим ключом, чем служба таблиц, поэтому следует соблюдать осторожность при создании строки для подписи для авторизации. Тип blob, BlockBlob или PageBlob , должен быть указан в качестве заголовка запроса и, следовательно, появляется в строке авторизации.

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"];
    }
}

Это генерирует следующий запрос:

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

Тело запроса:

   Andrew Carnegie was born in Dunfermline

Служба BLOB-объектов генерирует следующий ответ:

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

Аренда Blob

Служба BLOB-объектов позволяет пользователю арендовать большой двоичный объект за раз и таким образом получить блокировку записи на него. Вариант использования для этого — блокировка блоба страницы, используемого для хранения виртуального жесткого диска, поддерживающего записываемый диск Azure.

Пример LeaseBlob () в этом разделе демонстрирует тонкую проблему с созданием строк авторизации. URL содержит строку запроса, comp = lease . Вместо того, чтобы использовать это непосредственно при создании строки авторизации, она должна быть преобразована в comp: lease с двоеточием, заменяющим равный символ — см. В разделе modifyURL в примере. Кроме того, для операции Bloase Blob требуется использовать действие x-ms-lease-action, чтобы указать, приобретается ли договор аренды, продлевается, освобождается или нарушается.

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"];
    }
}

Это генерирует следующий запрос:

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

Служба BLOB-объектов генерирует следующий ответ:

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

API службы очередей

API Queue Service поддерживает следующие операции очереди на уровне:

API Queue Service поддерживает следующие операции очереди на уровне:

API Queue Service поддерживает следующие операции на уровне сообщений:

В этом разделе приведены примеры операций «Поместить сообщение» и «Получить сообщение».

Поместить сообщение

Самым очевидным любопытством к Put Message является то, что он использует HTTP-глагол POST, а не PUT . Вероятно, проблема заключается во взаимодействии английского языка и стандарта HTTP, который гласит, что PUT должен быть идемпотентным и что операция Put Message явно не выполняется, поскольку каждый вызов просто добавляет другое сообщение в очередь. Несмотря на это, меня поймали, когда я не смог прочитать документацию достаточно хорошо — так что воспринимайте это как предупреждение.

Содержимое сообщения, отправляемого в очередь, должно быть отформатировано в указанной схеме XML, а затем должно быть закодировано в формате UTF8.

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"];
    }
}

Это генерирует следующий запрос:

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

Тело запроса:

    <QueueMessage><MessageText>Saturday in the cafe</MessageText></QueueMessage>

Служба очереди генерирует следующий ответ:

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

Получать сообщения

Операция «Получение сообщений», описанная в этом разделе, извлекает одно сообщение с тайм-аутом видимости сообщения по умолчанию, равным 30 секундам.

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();
        }
    }
}

Это генерирует следующий запрос:

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

Служба очереди генерирует следующий ответ:

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

Сообщение возвращается в теле ответа следующим образом:

    <?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>

Я заметил, что некоторые спецификаторы новой строки в строках (\ n) были потеряны, когда блог был автоматически перенесен из Windows Live Spaces в WordPress. Я положил их обратно, но, возможно, я пропустил некоторые. Следовательно, в случае возникновения проблемы вы должны проверить переводы строк в canonicalizedHeaders и stringToSign .