Accessing Azure storage with Raw REST API

Sergei Meleshchuk - blog 

Contents

Introduction. 1

Example. 2

Client code: read a message from a queue. 2

Missing parts. 3

QueueBase. 3

ServiceEndpoint. 3

BuildSignature. 3

BuildUri 4

BuildRequest. 4

ExecuteRequest. 4

DecodeMessages. 5

CurrentMessageList. 6

MessageBase. 6

 

 

Introduction

Azure SDK defines [Raw] REST API, and supplies a client library which isolates a developer from REST API. The client library is a good thing.

Sometimes, however, you may want much thinner layer (than the client library) on top of REST API. The reasons include:

-          You want to understand the Azure REST API, without tearing your way thru the code of the client library,

-          You want to really understand the client library code, and need to start with a much simpler version of it.

I describe here what is probably the thinnest possible layer on top of the Azure REST API. This “Mini storage library” is easy to learn and understand, and it can be used on its own to access the Azure storage. I am totally ignoring (in this post) the needs of table storage. I chose to concentrate on queue and blob storage operations instead.

The “mini library” a very small number of short functions, which you can completely inline if you want. The class structure is close to absent as well.

Example

I will show how to read a message from the queue storage. The code is, sure, more lengthy then in case of using the client library. However, this code if very close “to the metal”, and you can factor out the boilerplate part very easily. I did not do this intentionally.

The code is followed by the description of the missing parts – the small set of small functions.

Client code: read a message from a queue

 

        private void LibReadOne(object sender, RoutedEventArgs e)

        {

            TimeSpan elapsed;

 

            // Read configuration data: service URI, account name, access key

 

            string queueName = "registration2";

            QueueBase queue = new QueueBase(queueName);

 

            string date = DateTime.UtcNow.ToString("R", CultureInfo.InvariantCulture);

 

            string method = "GET";

            string suffix = queueName + "/" + "messages";

            string signature = BuildSignature(method, date, queue.Account,

                queue.Key, suffix);

            suffix += "?numofmessages=1&visibilitytimeout=5";

            Uri uri = BuildUri(method, queue, suffix);

            HttpWebRequest request = BuildRequest(method, uri, queue.Account,

                signature, date);

            DateTime starting = DateTime.Now;

            XmlDocument doc = ExecuteRequest(request, out elapsed);

            Trace("ExeReq time {0}\n", DateTime.Now - starting);

            lock (CurrentMessageListLock)

            {

                /*List<MessageBase>*/

                CurrentMessageList = DecodeMessages(doc);

            }

            //Trace("Decode time {0}\n", DateTime.Now - starting);

            if (CurrentMessageList != null)

                foreach (MessageBase mm in CurrentMessageList)

                    Trace("[MessageText] {0}\n", mm.MessageText);

            else

                Trace("[No messages]\n");

        }

 

Missing parts

 

Below are all the class definition, and low-level functions, needed for the above code to work.

QueueBase

        class QueueBase : ServiceEndpoint

        {

            public string QueueName { get; set; }

            public QueueBase(string queueName)

            {

                this.QueueName = queueName;

                base.BaseUri = new Uri(ConfigurationSettings.AppSettings["QueueStorageEndpoint"]);

                base.Account = ConfigurationSettings.AppSettings["AccountName"];

                base.Base64Key = ConfigurationSettings.AppSettings["AccountSharedKey"];

            }

        }

 

ServiceEndpoint

    public class ServiceEndpoint

    {

        public Uri BaseUri { get; set; }

        public string Account { get; set; }

        public string Base64Key { private get; set; }

        public byte[] Key

        {

            get { return Convert.FromBase64String(Base64Key); }

        }

    }

 

BuildSignature

 

        string BuildSignature(

            string method,

            string date,

            string accountName,

            byte[] sharedKey,

            string suffix)

        {

            string signPart1 = string.Format(CultureInfo.InvariantCulture,

                "{0}\n{1}\n{2}\n{3}\n{4}\n/",

                method,                 // 0 - HTTP Method, like GET

                string.Empty,           // 1 - "Content-MD5" header value. Leave blank.

                string.Empty,           // 2 - Content type, like MIME. Leave blank.

                string.Empty,           // 3 - Legacy "date" header. Leave blank.

                "x-ms-date:" + date);   // 4 - Azure form of date header.

 

            string signPart2 = accountName + "/" + suffix;

            string signatureString = signPart1 + signPart2;

 

            byte[] signBytes = System.Text.Encoding.UTF8.GetBytes(signatureString);

            byte[] hash = new HMACSHA256(sharedKey).ComputeHash(signBytes);

            string signature = System.Convert.ToBase64String(hash);

            return signature;

        }

 

BuildUri

 

        Uri BuildUri(string method, ServiceEndpoint svc, string suffix)

        {

            string accountUriString = string.Format(

                CultureInfo.InvariantCulture,

                "{0}{1}{2}.{3}",

                svc.BaseUri.Scheme,

                Uri.SchemeDelimiter,

                svc.Account,

                svc.BaseUri.Host);

 

            string divisorToken = suffix.Contains('?') ? "&" : "?";

            string uriString = string.Format(CultureInfo.InvariantCulture,

                "{0}/{1}{2}{3}",

                accountUriString,

                suffix,

                divisorToken,

                "timeout=30");

            Uri uri = new Uri(uriString);

            return uri;

        }

 

BuildRequest

 

        static HttpWebRequest BuildRequest(

            string method,

            Uri uri,

            string accountName,

            string signature,

            string date)

        {

            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(uri);

            request.Timeout = 30 * 1000;

            request.ReadWriteTimeout = 30 * 1000;

            request.Method = method;

            request.ContentLength = 0;

            request.Headers.Add("x-ms-date", date);

            string AuthHeaderValue = string.Format(CultureInfo.InvariantCulture,

                                          "SharedKey {0}:{1}",

                                          accountName,

                                          signature);

            request.Headers.Add("Authorization", AuthHeaderValue);

            return request;

        }

 

ExecuteRequest

 

        XmlDocument ExecuteRequest(

            HttpWebRequest request,

            out TimeSpan elapsed)

        {

            DateTime startTime = DateTime.Now;

            elapsed = TimeSpan.MinValue;

            try

            {

                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())

                {

                    if (response.StatusCode == HttpStatusCode.OK)

                    {

                        using (Stream stream = response.GetResponseStream())

                        {

                            elapsed = DateTime.Now - startTime;

                            XmlDocument doc = new XmlDocument();

                            try

                            {

#if (DIRECTPARSE)

                                byte[] data = new byte[10000];

                                int length = stream.Read(data, 0, 10000);

                                string dataString = Encoding.UTF8.GetString(data, 0, length);

#endif

                                doc.Load(stream);   // doc = output param

                                stream.Close();

                                response.Close();

                                return doc;        // indicate success

                            }

                            catch (XmlException xe)

                            {

                                stream.Close();

                                response.Close();

                                throw xe;

                            }

                        }

                    }

                    else

                    {

                        string errorMessage = response.ToString();

                        throw new InvalidOperationException("Retcode wrong " + errorMessage);

                    }

                }

            }

            catch (WebException we)

            {

                if (we.Response != null)

                {

                    HttpWebResponse resp = (HttpWebResponse)we.Response;

                    string description = resp.StatusDescription;

                    if (description != null)

                    {

                        throw new InvalidOperationException(description);

                    }

                }

                throw new InvalidOperationException(we.ToString());

            }

        }

 

DecodeMessages

 

        List<MessageBase> DecodeMessages(XmlDocument doc)

        {

            List<MessageBase> readMessages = null;

            MessageBase msg;

            XmlNodeList messagesNodes = doc.SelectNodes("//QueueMessage");

            if (messagesNodes.Count == 0)

            {

                return null;

            }

            readMessages = new List<MessageBase>();

            foreach (XmlNode messageNode in messagesNodes)

            {

                msg = new MessageBase();

                msg.MessageId = messageNode.SelectSingleNode("MessageId").FirstChild.Value.Trim();

                Debug.Assert(msg.MessageId != null);

                if (messageNode.SelectSingleNode("PopReceipt") != null)

                {

                    msg.MessagePopReceipt = messageNode.SelectSingleNode("PopReceipt").FirstChild.Value.Trim();

                    Debug.Assert(msg.MessagePopReceipt != null);

                }

                string base64 = messageNode.SelectSingleNode("MessageText").FirstChild.Value.Trim();

                byte[] messageBytes = Convert.FromBase64String(base64);

                msg.MessageText = Encoding.UTF8.GetString(messageBytes, 0, messageBytes.Length);

                readMessages.Add(msg);

            }

            return readMessages;

        }

 

CurrentMessageList

 

        List<MessageBase> CurrentMessageList;

 

MessageBase

 

        class MessageBase

        {

            public string MessageId { get; set; }

            public string MessageText { get; set; }

            public string MessagePopReceipt { get; set; }

        }