Move to 2012 and the Async Targeting pack
[pithos-ms-client] / trunk / Pithos.Network / RestClient.cs
index 1a5d52f..9ba0e7b 100644 (file)
-// -----------------------------------------------------------------------
-// <copyright file="RestClient.cs" company="Microsoft">
-// TODO: Update copyright text.
-// </copyright>
-// -----------------------------------------------------------------------
-
-using System.Collections.Specialized;
-using System.Diagnostics;
-using System.Diagnostics.Contracts;
-using System.IO;
-using System.Net;
-using System.Runtime.Serialization;
-using System.Threading.Tasks;
-using log4net;
-
-
-namespace Pithos.Network
-{
-    using System;
-    using System.Collections.Generic;
-    using System.Linq;
-    using System.Text;
-
-    /// <summary>
-    /// TODO: Update summary.
-    /// </summary>
-    public class RestClient:WebClient
-    {
-        public int Timeout { get; set; }
-
-        public bool TimedOut { get; set; }
-
-        public HttpStatusCode StatusCode { get; private set; }
-
-        public string StatusDescription { get; set; }
-
-        public long? RangeFrom { get; set; }
-        public long? RangeTo { get; set; }
-
-        public int Retries { get; set; }
-
-        private readonly Dictionary<string, string> _parameters=new Dictionary<string, string>();
-        public Dictionary<string, string> Parameters
-        {
-            get
-            {
-                Contract.Ensures(_parameters!=null);
-                return _parameters;
-            }            
-        }
-
-        private static readonly ILog Log = LogManager.GetLogger("RestClient");
-
-
-        [ContractInvariantMethod]
-        private void Invariants()
-        {
-            Contract.Invariant(Headers!=null);    
-        }
-
-        public RestClient():base()
-        {
-            
-        }
-
-       
-        public RestClient(RestClient other)
-            : base()
-        {
-            if (other==null)
-                throw new ArgumentNullException("other");
-            Contract.EndContractBlock();
-
-            CopyHeaders(other);
-            Timeout = other.Timeout;
-            Retries = other.Retries;
-            BaseAddress = other.BaseAddress;             
-
-            foreach (var parameter in other.Parameters)
-            {
-                Parameters.Add(parameter.Key,parameter.Value);
-            }
-
-            this.Proxy = other.Proxy;
-        }
-
-        protected override WebRequest GetWebRequest(Uri address)
-        {
-            TimedOut = false;
-            var webRequest = base.GetWebRequest(address);
-            var request = (HttpWebRequest)webRequest;
-            if (IfModifiedSince.HasValue)
-                request.IfModifiedSince = IfModifiedSince.Value;
-            request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
-            if(Timeout>0)
-                request.Timeout = Timeout;
-
-            if (RangeFrom.HasValue)
-            {
-                if (RangeTo.HasValue)
-                    request.AddRange(RangeFrom.Value, RangeTo.Value);
-                else
-                    request.AddRange(RangeFrom.Value);
-            }
-            return request; 
-        }
-
-        public DateTime? IfModifiedSince { get; set; }
-
-        protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result)
-        {
-            return ProcessResponse(()=>base.GetWebResponse(request, result)); 
-        }
-
-        protected override WebResponse GetWebResponse(WebRequest request)
-        {
-            return ProcessResponse(() => base.GetWebResponse(request));
-        }
-
-        private WebResponse ProcessResponse(Func<WebResponse> getResponse)
-        {
-            try
-            {
-                var response = (HttpWebResponse)getResponse();
-                StatusCode = response.StatusCode;
-                LastModified = response.LastModified;
-                StatusDescription = response.StatusDescription;
-                return response;
-            }
-            catch (WebException exc)
-            {
-                if (exc.Response != null)
-                {
-                    var response = (exc.Response as HttpWebResponse);
-                    if (AllowedStatusCodes.Contains(response.StatusCode))
-                    {
-                        StatusCode = response.StatusCode;
-                        LastModified = response.LastModified;
-                        StatusDescription = response.StatusDescription;
-
-                        return response;
-                    }
-                    if (exc.Response.ContentLength > 0)
-                    {
-                        string content = GetContent(exc.Response);
-                        Log.ErrorFormat(content);                        
-                    }
-                }
-                throw;
-            }
-        }
-
-        private readonly List<HttpStatusCode> _allowedStatusCodes=new List<HttpStatusCode>{HttpStatusCode.NotModified};
-        public List<HttpStatusCode> AllowedStatusCodes
-        {
-            get
-            {
-                return _allowedStatusCodes;
-            }            
-        }
-
-        public DateTime LastModified { get; private set; }
-
-        private static string GetContent(WebResponse webResponse)
-        {
-            if (webResponse == null)
-                throw new ArgumentNullException("webResponse");
-            Contract.EndContractBlock();
-
-            string content;
-            using (var stream = webResponse.GetResponseStream())
-            using (var reader = new StreamReader(stream))
-            {
-                content = reader.ReadToEnd();
-            }
-            return content;
-        }
-
-        public string DownloadStringWithRetry(string address,int retries=0)
-        {
-            
-            if (address == null)
-                throw new ArgumentNullException("address");
-
-            var actualAddress = GetActualAddress(address);
-
-            TraceStart("GET",actualAddress);            
-            
-            var actualRetries = (retries == 0) ? Retries : retries;
-
-            
-            var task = Retry(() =>
-            {
-                var uriString = String.Join("/", BaseAddress.TrimEnd('/'), actualAddress);                
-                var content = base.DownloadString(uriString);
-
-                if (StatusCode == HttpStatusCode.NoContent)
-                    return String.Empty;
-                return content;
-
-            }, actualRetries);
-
-            var result = task.Result;
-            return result;
-        }
-
-        public void Head(string address,int retries=0)
-        {
-            AllowedStatusCodes.Add(HttpStatusCode.NotFound);
-            RetryWithoutContent(address, retries, "HEAD");
-        }
-
-        public void PutWithRetry(string address, int retries = 0)
-        {
-            RetryWithoutContent(address, retries, "PUT");
-        }
-
-        public void DeleteWithRetry(string address,int retries=0)
-        {
-            RetryWithoutContent(address, retries, "DELETE");
-        }
-
-        public string GetHeaderValue(string headerName,bool optional=false)
-        {
-            if (this.ResponseHeaders==null)
-                throw new InvalidOperationException("ResponseHeaders are null");
-            Contract.EndContractBlock();
-
-            var values=this.ResponseHeaders.GetValues(headerName);
-            if (values != null)
-                return values[0];
-
-            if (optional)            
-                return null;            
-            //A required header was not found
-            throw new WebException(String.Format("The {0}  header is missing", headerName));
-        }
-
-        public void SetNonEmptyHeaderValue(string headerName, string value)
-        {
-            if (String.IsNullOrWhiteSpace(value))
-                return;
-            Headers.Add(headerName,value);
-        }
-
-        private void RetryWithoutContent(string address, int retries, string method)
-        {
-            if (address == null)
-                throw new ArgumentNullException("address");
-
-            var actualAddress = GetActualAddress(address);            
-            var actualRetries = (retries == 0) ? Retries : retries;
-
-            var task = Retry(() =>
-            {
-                var uriString = String.Join("/",BaseAddress ,actualAddress);
-                var uri = new Uri(uriString);
-                var request =  GetWebRequest(uri);
-                request.Method = method;
-                if (ResponseHeaders!=null)
-                    ResponseHeaders.Clear();
-
-                TraceStart(method, uriString);
-                if (method == "PUT")
-                    request.ContentLength = 0;
-                var response = (HttpWebResponse)GetWebResponse(request);
-                StatusCode = response.StatusCode;
-                StatusDescription = response.StatusDescription;                
-                
-
-                return 0;
-            }, actualRetries);
-
-            try
-            {
-                task.Wait();
-            }
-            catch (AggregateException ex)
-            {
-                var exc = ex.InnerException;
-                if (exc is RetryException)
-                {
-                    Log.ErrorFormat("[{0}] RETRY FAILED for {1} after {2} retries",method,address,retries);
-                }
-                else
-                {
-                    Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, exc);
-                }
-                throw exc;
-
-            }
-            catch(Exception ex)
-            {
-                Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, ex);
-                throw;
-            }
-        }
-        
-        private static void TraceStart(string method, string actualAddress)
-        {
-            Log.InfoFormat("[{0}] {1} {2}", method, DateTime.Now, actualAddress);
-        }
-
-        private string GetActualAddress(string address)
-        {
-            if (Parameters.Count == 0)
-                return address;
-            var addressBuilder=new StringBuilder(address);            
-
-            bool isFirst = true;
-            foreach (var parameter in Parameters)
-            {
-                if(isFirst)
-                    addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value);
-                else
-                    addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value);
-                isFirst = false;
-            }
-            return addressBuilder.ToString();
-        }
-
-        public string DownloadStringWithRetry(Uri address,int retries=0)
-        {
-            if (address == null)
-                throw new ArgumentNullException("address");
-
-            var actualRetries = (retries == 0) ? Retries : retries;            
-            var task = Retry(() =>
-            {
-                var content = base.DownloadString(address);
-
-                if (StatusCode == HttpStatusCode.NoContent)
-                    return String.Empty;
-                return content;
-
-            }, actualRetries);
-
-            var result = task.Result;
-            return result;
-        }
-
-      
-        /// <summary>
-        /// Copies headers from another RestClient
-        /// </summary>
-        /// <param name="source">The RestClient from which the headers are copied</param>
-        public void CopyHeaders(RestClient source)
-        {
-            if (source == null)
-                throw new ArgumentNullException("source", "source can't be null");
-            Contract.EndContractBlock();
-            //The Headers getter initializes the property, it is never null
-            Contract.Assume(Headers!=null);
-                
-            CopyHeaders(source.Headers,Headers);
-        }
-        
-        /// <summary>
-        /// Copies headers from one header collection to another
-        /// </summary>
-        /// <param name="source">The source collection from which the headers are copied</param>
-        /// <param name="target">The target collection to which the headers are copied</param>
-        public static void CopyHeaders(WebHeaderCollection source,WebHeaderCollection target)
-        {
-            if (source == null)
-                throw new ArgumentNullException("source", "source can't be null");
-            if (target == null)
-                throw new ArgumentNullException("target", "target can't be null");
-            Contract.EndContractBlock();
-
-            for (int i = 0; i < source.Count; i++)
-            {
-                target.Add(source.GetKey(i), source[i]);
-            }            
-        }
-
-        public void AssertStatusOK(string message)
-        {
-            if (StatusCode >= HttpStatusCode.BadRequest)
-                throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription));
-        }
-
-
-        private Task<T> Retry<T>(Func<T> original, int retryCount, TaskCompletionSource<T> tcs = null)
-        {
-            if (original==null)
-                throw new ArgumentNullException("original");
-            Contract.EndContractBlock();
-
-            if (tcs == null)
-                tcs = new TaskCompletionSource<T>();
-            Task.Factory.StartNew(original).ContinueWith(_original =>
-                {
-                    if (!_original.IsFaulted)
-                        tcs.SetFromTask(_original);
-                    else 
-                    {
-                        var e = _original.Exception.InnerException;
-                        var we = (e as WebException);
-                        if (we==null)
-                            tcs.SetException(e);
-                        else
-                        {
-                            var statusCode = GetStatusCode(we);
-
-                            //Return null for 404
-                            if (statusCode == HttpStatusCode.NotFound)
-                                tcs.SetResult(default(T));
-                            //Retry for timeouts and service unavailable
-                            else if (we.Status == WebExceptionStatus.Timeout ||
-                                (we.Status == WebExceptionStatus.ProtocolError && statusCode == HttpStatusCode.ServiceUnavailable))
-                            {
-                                TimedOut = true;
-                                if (retryCount == 0)
-                                {                                    
-                                    Log.ErrorFormat("[ERROR] Timed out too many times. \n{0}\n",e);
-                                    tcs.SetException(new RetryException("Timed out too many times.", e));                                    
-                                }
-                                else
-                                {
-                                    Log.ErrorFormat(
-                                        "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout,
-                                        retryCount, e);
-                                    Retry(original, retryCount - 1, tcs);
-                                }
-                            }
-                            else
-                                tcs.SetException(e);
-                        }
-                    };
-                });
-            return tcs.Task;
-        }
-
-        private HttpStatusCode GetStatusCode(WebException we)
-        {
-            if (we==null)
-                throw new ArgumentNullException("we");
-            var statusCode = HttpStatusCode.RequestTimeout;
-            if (we.Response != null)
-            {
-                statusCode = ((HttpWebResponse) we.Response).StatusCode;
-                this.StatusCode = statusCode;
-            }
-            return statusCode;
-        }
-
-        public UriBuilder GetAddressBuilder(string container, string objectName)
-        {
-            var builder = new UriBuilder(String.Join("/", BaseAddress, container, objectName));
-            return builder;
-        }
-    }
-
-    public class RetryException:Exception
-    {
-        public RetryException()
-            :base()
-        {
-            
-        }
-
-        public RetryException(string message)
-            :base(message)
-        {
-            
-        }
-
-        public RetryException(string message,Exception innerException)
-            :base(message,innerException)
-        {
-            
-        }
-
-        public RetryException(SerializationInfo info,StreamingContext context)
-            :base(info,context)
-        {
-            
-        }
-    }
-}
+#region\r
+/* -----------------------------------------------------------------------\r
+ * <copyright file="RestClient.cs" company="GRNet">\r
+ * \r
+ * Copyright 2011-2012 GRNET S.A. All rights reserved.\r
+ *\r
+ * Redistribution and use in source and binary forms, with or\r
+ * without modification, are permitted provided that the following\r
+ * conditions are met:\r
+ *\r
+ *   1. Redistributions of source code must retain the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer.\r
+ *\r
+ *   2. Redistributions in binary form must reproduce the above\r
+ *      copyright notice, this list of conditions and the following\r
+ *      disclaimer in the documentation and/or other materials\r
+ *      provided with the distribution.\r
+ *\r
+ *\r
+ * THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS\r
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\r
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR\r
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\r
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT\r
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF\r
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED\r
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT\r
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN\r
+ * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE\r
+ * POSSIBILITY OF SUCH DAMAGE.\r
+ *\r
+ * The views and conclusions contained in the software and\r
+ * documentation are those of the authors and should not be\r
+ * interpreted as representing official policies, either expressed\r
+ * or implied, of GRNET S.A.\r
+ * </copyright>\r
+ * -----------------------------------------------------------------------\r
+ */\r
+#endregion\r
+using System.Collections.Specialized;\r
+using System.Diagnostics;\r
+using System.Diagnostics.Contracts;\r
+using System.IO;\r
+using System.Net;\r
+using System.Reflection;\r
+using System.Runtime.Serialization;\r
+using System.Threading;\r
+using System.Threading.Tasks;\r
+using log4net;\r
+\r
+\r
+namespace Pithos.Network\r
+{\r
+    using System;\r
+    using System.Collections.Generic;\r
+    using System.Linq;\r
+    using System.Text;\r
+\r
+    /// <summary>\r
+    /// TODO: Update summary.\r
+    /// </summary>\r
+    public class RestClient:WebClient\r
+    {\r
+        private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\r
+\r
+        public int Timeout { get; set; }\r
+\r
+        public bool TimedOut { get; set; }\r
+\r
+        public HttpStatusCode StatusCode { get; private set; }\r
+\r
+        public string StatusDescription { get; set; }\r
+\r
+        public long? RangeFrom { get; set; }\r
+        public long? RangeTo { get; set; }\r
+\r
+        public int Retries { get; set; }\r
+\r
+        private readonly Dictionary<string, string> _parameters=new Dictionary<string, string>();\r
+        public Dictionary<string, string> Parameters\r
+        {\r
+            get\r
+            {\r
+                Contract.Ensures(_parameters!=null);\r
+                return _parameters;\r
+            }            \r
+        }\r
+\r
+\r
+        [ContractInvariantMethod]\r
+        private void Invariants()\r
+        {\r
+            Contract.Invariant(Headers!=null);    \r
+        }\r
+\r
+        public RestClient():base()\r
+        {\r
+            //The maximum error response must be large because missing server hashes are return as a Conflivt (409) error response\r
+            //Any value above 2^21-1 will result in an empty response.\r
+            //-1 essentially ignores the maximum length\r
+            HttpWebRequest.DefaultMaximumErrorResponseLength = -1;\r
+        }\r
+\r
+       \r
+        public RestClient(RestClient other)\r
+            : base()\r
+        {\r
+            if (other==null)\r
+                //Log.ErrorFormat("[ERROR] No parameters provided to the rest client. \n{0}\n", other);\r
+                throw new ArgumentNullException("other");\r
+            Contract.EndContractBlock();\r
+\r
+            //The maximum error response must be large because missing server hashes are return as a Conflivt (409) error response\r
+            //Any value above 2^21-1 will result in an empty response.\r
+            //-1 essentially ignores the maximum length\r
+            HttpWebRequest.DefaultMaximumErrorResponseLength = -1;\r
+            \r
+\r
+            CopyHeaders(other);\r
+            Timeout = other.Timeout;\r
+            Retries = other.Retries;\r
+            BaseAddress = other.BaseAddress;             \r
+\r
+            foreach (var parameter in other.Parameters)\r
+            {\r
+                Parameters.Add(parameter.Key,parameter.Value);\r
+            }\r
+\r
+            this.Proxy = other.Proxy;\r
+        }\r
+\r
+\r
+        protected override WebRequest GetWebRequest(Uri address)\r
+        {\r
+            TimedOut = false;\r
+            var webRequest = base.GetWebRequest(address);            \r
+            var request = (HttpWebRequest)webRequest;\r
+            request.CookieContainer=new CookieContainer();\r
+            request.ServicePoint.ConnectionLimit = 50;\r
+            if (IfModifiedSince.HasValue)\r
+                request.IfModifiedSince = IfModifiedSince.Value;\r
+            request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;\r
+            if(Timeout>0)\r
+                request.Timeout = Timeout;\r
+\r
+            if (RangeFrom.HasValue)\r
+            {\r
+                if (RangeTo.HasValue)\r
+                    request.AddRange(RangeFrom.Value, RangeTo.Value);\r
+                else\r
+                    request.AddRange(RangeFrom.Value);\r
+            }\r
+            return request; \r
+        }\r
+\r
+        public DateTime? IfModifiedSince { get; set; }\r
+\r
+        //Asynchronous version\r
+        protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result)\r
+        {            \r
+            Log.InfoFormat("[{0}] {1}", request.Method, request.RequestUri); \r
+            HttpWebResponse response = null;\r
+\r
+            try\r
+            {\r
+                response = (HttpWebResponse)base.GetWebResponse(request, result);\r
+            }\r
+            catch (WebException exc)\r
+            {\r
+                if (!TryGetResponse(exc, request,out response))\r
+                    throw;\r
+            }\r
+\r
+            StatusCode = response.StatusCode;\r
+            LastModified = response.LastModified;\r
+            StatusDescription = response.StatusDescription;\r
+            return response;\r
+\r
+        }\r
+      \r
+\r
+        //Synchronous version\r
+        protected override WebResponse GetWebResponse(WebRequest request)\r
+        {\r
+            HttpWebResponse response = null;\r
+            try\r
+            {           \r
+                Log.InfoFormat("[{0}] {1}",request.Method,request.RequestUri);     \r
+                response = (HttpWebResponse)base.GetWebResponse(request);\r
+            }\r
+            catch (WebException exc)\r
+            {\r
+                if (!TryGetResponse(exc, request,out response))\r
+                    throw;\r
+            }\r
+\r
+            StatusCode = response.StatusCode;\r
+            LastModified = response.LastModified;\r
+            StatusDescription = response.StatusDescription;\r
+            return response;\r
+        }\r
+\r
+        private bool TryGetResponse(WebException exc, WebRequest request,out HttpWebResponse response)\r
+        {\r
+            response = null;\r
+            //Fail on empty response\r
+            if (exc.Response == null)\r
+            {\r
+                Log.WarnFormat("[{0}] {1} {2}", request.Method, exc.Status, request.RequestUri);     \r
+                return false;\r
+            }\r
+\r
+            response = (exc.Response as HttpWebResponse);\r
+            var statusCode = (int)response.StatusCode;\r
+            //Succeed on allowed status codes\r
+            if (AllowedStatusCodes.Contains(response.StatusCode))\r
+            {\r
+                if (Log.IsDebugEnabled)\r
+                    Log.DebugFormat("[{0}] {1} {2}", request.Method, statusCode, request.RequestUri);     \r
+                return true;\r
+            }\r
+            \r
+            Log.WarnFormat("[{0}] {1} {2}", request.Method, statusCode, request.RequestUri);\r
+\r
+            //Does the response have any content to log?\r
+            if (exc.Response.ContentLength > 0)\r
+            {\r
+                var content = LogContent(exc.Response);\r
+                Log.ErrorFormat(content);\r
+            }\r
+            return false;\r
+        }\r
+\r
+        private readonly List<HttpStatusCode> _allowedStatusCodes=new List<HttpStatusCode>{HttpStatusCode.NotModified};        \r
+\r
+        public List<HttpStatusCode> AllowedStatusCodes\r
+        {\r
+            get\r
+            {\r
+                return _allowedStatusCodes;\r
+            }            \r
+        }\r
+\r
+        public DateTime LastModified { get; private set; }\r
+\r
+        private static string LogContent(WebResponse webResponse)\r
+        {\r
+            if (webResponse == null)\r
+                throw new ArgumentNullException("webResponse");\r
+            Contract.EndContractBlock();\r
+\r
+            //The response stream must be copied to avoid affecting other code by disposing of the \r
+            //original response stream.\r
+            var stream = webResponse.GetResponseStream();            \r
+            using(var memStream=new MemoryStream())\r
+            using (var reader = new StreamReader(memStream))\r
+            {\r
+                stream.CopyTo(memStream);                \r
+                string content = reader.ReadToEnd();\r
+\r
+                stream.Seek(0,SeekOrigin.Begin);\r
+                return content;\r
+            }\r
+        }\r
+\r
+        public string DownloadStringWithRetry(string address,int retries=0)\r
+        {\r
+            \r
+            if (address == null)\r
+                throw new ArgumentNullException("address");\r
+\r
+            var actualAddress = GetActualAddress(address);\r
+\r
+            TraceStart("GET",actualAddress);            \r
+            \r
+            var actualRetries = (retries == 0) ? Retries : retries;\r
+\r
+            var uriString = String.Join("/", BaseAddress.TrimEnd('/'), actualAddress);\r
+\r
+            var task = Retry(() =>\r
+            {                                \r
+                var content = DownloadString(uriString);\r
+\r
+                if (StatusCode == HttpStatusCode.NoContent)\r
+                    return String.Empty;\r
+                return content;\r
+\r
+            }, actualRetries);\r
+\r
+            try\r
+            {\r
+                    var result = task.Result;\r
+                return result;\r
+\r
+            }\r
+            catch (AggregateException exc)\r
+            {\r
+                //If the task fails, propagate the original exception\r
+                if (exc.InnerException!=null)\r
+                    throw exc.InnerException;\r
+                throw;\r
+            }\r
+        }\r
+\r
+        public void Head(string address,int retries=0)\r
+        {\r
+            AllowedStatusCodes.Add(HttpStatusCode.NotFound);\r
+            RetryWithoutContent(address, retries, "HEAD");\r
+        }\r
+\r
+        public void PutWithRetry(string address, int retries = 0, string contentType=null)\r
+        {\r
+            RetryWithoutContent(address, retries, "PUT",contentType);\r
+        }\r
+\r
+        public void PostWithRetry(string address,string contentType)\r
+        {            \r
+            RetryWithoutContent(address, 0, "POST",contentType);\r
+        }\r
+\r
+        public void DeleteWithRetry(string address,int retries=0)\r
+        {\r
+            RetryWithoutContent(address, retries, "DELETE");\r
+        }\r
+\r
+        public string GetHeaderValue(string headerName,bool optional=false)\r
+        {\r
+            if (this.ResponseHeaders==null)\r
+                throw new InvalidOperationException("ResponseHeaders are null");\r
+            Contract.EndContractBlock();\r
+\r
+            var values=this.ResponseHeaders.GetValues(headerName);\r
+            if (values != null)\r
+                return values[0];\r
+\r
+            if (optional)            \r
+                return null;            \r
+            //A required header was not found\r
+            throw new WebException(String.Format("The {0}  header is missing", headerName));\r
+        }\r
+\r
+        public void SetNonEmptyHeaderValue(string headerName, string value)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(value))\r
+                return;\r
+            Headers.Add(headerName,value);\r
+        }\r
+\r
+        private void RetryWithoutContent(string address, int retries, string method,string contentType=null)\r
+        {\r
+            if (address == null)\r
+                throw new ArgumentNullException("address");\r
+\r
+            var actualAddress = GetActualAddress(address);            \r
+            var actualRetries = (retries == 0) ? Retries : retries;\r
+\r
+            var task = Retry(() =>\r
+            {\r
+                var uriString = String.Join("/",BaseAddress ,actualAddress);\r
+                var uri = new Uri(uriString);\r
+                var request =  GetWebRequest(uri);\r
+                if (contentType!=null)\r
+                {\r
+                    request.ContentType = contentType;\r
+                    request.ContentLength = 0;\r
+                }\r
+                request.Method = method;\r
+                if (ResponseHeaders!=null)\r
+                    ResponseHeaders.Clear();\r
+\r
+                TraceStart(method, uriString);\r
+                if (method == "PUT")\r
+                    request.ContentLength = 0;\r
+\r
+                //Have to use try/finally instead of using here, because WebClient needs a valid WebResponse object\r
+                //in order to return response headers\r
+                var response = (HttpWebResponse)GetWebResponse(request);\r
+                try\r
+                {\r
+                    LastModified = response.LastModified;\r
+                    StatusCode = response.StatusCode;\r
+                    StatusDescription = response.StatusDescription;\r
+                }\r
+                finally\r
+                {\r
+                    response.Close();\r
+                }\r
+                \r
+\r
+                return 0;\r
+            }, actualRetries);\r
+\r
+            try\r
+            {\r
+                task.Wait();\r
+            }\r
+            catch (AggregateException ex)\r
+            {\r
+                var exc = ex.InnerException;\r
+                if (exc is RetryException)\r
+                {\r
+                    Log.ErrorFormat("[{0}] RETRY FAILED for {1} after {2} retries",method,address,retries);\r
+                }\r
+                else\r
+                {\r
+                    Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, exc);\r
+                }\r
+                throw exc;\r
+\r
+            }\r
+            catch(Exception ex)\r
+            {\r
+                Log.ErrorFormat("[{0}] FAILED for {1} with \n{2}", method, address, ex);\r
+                throw;\r
+            }\r
+        }\r
+        \r
+        private static void TraceStart(string method, string actualAddress)\r
+        {\r
+            Log.InfoFormat("[{0}] {1} {2}", method, DateTime.Now, actualAddress);\r
+        }\r
+\r
+        private string GetActualAddress(string address)\r
+        {\r
+            if (Parameters.Count == 0)\r
+                return address;\r
+            var addressBuilder=new StringBuilder(address);            \r
+\r
+            bool isFirst = true;\r
+            foreach (var parameter in Parameters)\r
+            {\r
+                if(isFirst)\r
+                    addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value);\r
+                else\r
+                    addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value);\r
+                isFirst = false;\r
+            }\r
+            return addressBuilder.ToString();\r
+        }\r
+\r
+        public string DownloadStringWithRetry(Uri address,int retries=0)\r
+        {\r
+            if (address == null)\r
+                throw new ArgumentNullException("address");\r
+\r
+            var actualRetries = (retries == 0) ? Retries : retries;            \r
+            var task = Retry(() =>\r
+            {\r
+                var content = base.DownloadString(address);\r
+\r
+                if (StatusCode == HttpStatusCode.NoContent)\r
+                    return String.Empty;\r
+                return content;\r
+\r
+            }, actualRetries);\r
+\r
+            var result = task.Result;\r
+            return result;\r
+        }\r
+\r
+      \r
+        /// <summary>\r
+        /// Copies headers from another RestClient\r
+        /// </summary>\r
+        /// <param name="source">The RestClient from which the headers are copied</param>\r
+        public void CopyHeaders(RestClient source)\r
+        {\r
+            if (source == null)\r
+                throw new ArgumentNullException("source", "source can't be null");\r
+            Contract.EndContractBlock();\r
+            //The Headers getter initializes the property, it is never null\r
+            Contract.Assume(Headers!=null);\r
+                \r
+            CopyHeaders(source.Headers,Headers);\r
+        }\r
+        \r
+        /// <summary>\r
+        /// Copies headers from one header collection to another\r
+        /// </summary>\r
+        /// <param name="source">The source collection from which the headers are copied</param>\r
+        /// <param name="target">The target collection to which the headers are copied</param>\r
+        public static void CopyHeaders(WebHeaderCollection source,WebHeaderCollection target)\r
+        {\r
+            if (source == null)\r
+                throw new ArgumentNullException("source", "source can't be null");\r
+            if (target == null)\r
+                throw new ArgumentNullException("target", "target can't be null");\r
+            Contract.EndContractBlock();\r
+\r
+            for (int i = 0; i < source.Count; i++)\r
+            {\r
+                target.Add(source.GetKey(i), source[i]);\r
+            }            \r
+        }\r
+\r
+        public void AssertStatusOK(string message)\r
+        {\r
+            if (StatusCode >= HttpStatusCode.BadRequest)\r
+                throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription));\r
+        }\r
+\r
+\r
+        private Task<T> Retry<T>(Func<T> original, int retryCount, TaskCompletionSource<T> tcs = null)\r
+        {\r
+            if (original==null)\r
+                throw new ArgumentNullException("original");\r
+            Contract.EndContractBlock();\r
+\r
+            if (tcs == null)\r
+                tcs = new TaskCompletionSource<T>();\r
+            Task.Factory.StartNew(original).ContinueWith(_original =>\r
+                {\r
+                    if (!_original.IsFaulted)\r
+                        tcs.SetFromTask(_original);\r
+                    else \r
+                    {\r
+                        var e = _original.Exception.InnerException;\r
+                        var we = (e as WebException);\r
+                        if (we==null)\r
+                            tcs.SetException(e);\r
+                        else\r
+                        {\r
+                            var statusCode = GetStatusCode(we);\r
+\r
+                            //Return null for 404\r
+                            if (statusCode == HttpStatusCode.NotFound)\r
+                                tcs.SetResult(default(T));\r
+                            //Retry for timeouts and service unavailable\r
+                            else if (we.Status == WebExceptionStatus.Timeout ||\r
+                                (we.Status == WebExceptionStatus.ProtocolError && statusCode == HttpStatusCode.ServiceUnavailable))\r
+                            {\r
+                                TimedOut = true;\r
+                                if (retryCount == 0)\r
+                                {                                    \r
+                                    Log.ErrorFormat("[ERROR] Timed out too many times. \n{0}\n",e);\r
+                                    tcs.SetException(new RetryException("Timed out too many times.", e));                                    \r
+                                }\r
+                                else\r
+                                {\r
+                                    Log.ErrorFormat(\r
+                                        "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout,\r
+                                        retryCount, e);\r
+                                    Retry(original, retryCount - 1, tcs);\r
+                                }\r
+                            }\r
+                            else\r
+                                tcs.SetException(e);\r
+                        }\r
+                    };\r
+                });\r
+            return tcs.Task;\r
+        }\r
+\r
+        private HttpStatusCode GetStatusCode(WebException we)\r
+        {\r
+            if (we==null)\r
+                throw new ArgumentNullException("we");\r
+            var statusCode = HttpStatusCode.RequestTimeout;\r
+            if (we.Response != null)\r
+            {\r
+                statusCode = ((HttpWebResponse) we.Response).StatusCode;\r
+                this.StatusCode = statusCode;\r
+            }\r
+            return statusCode;\r
+        }\r
+\r
+        public UriBuilder GetAddressBuilder(string container, string objectName)\r
+        {\r
+            var builder = new UriBuilder(String.Join("/", BaseAddress, container, objectName));\r
+            return builder;\r
+        }\r
+\r
+        public Dictionary<string, string> GetMeta(string metaPrefix)\r
+        {\r
+            if (String.IsNullOrWhiteSpace(metaPrefix))\r
+                throw new ArgumentNullException("metaPrefix");\r
+            Contract.EndContractBlock();\r
+\r
+            var keys = ResponseHeaders.AllKeys.AsQueryable();\r
+            var dict = (from key in keys\r
+                        where key.StartsWith(metaPrefix)\r
+                        let name = key.Substring(metaPrefix.Length)\r
+                        select new { Name = name, Value = ResponseHeaders[key] })\r
+                        .ToDictionary(t => t.Name, t => t.Value);\r
+            return dict;\r
+        }\r
+\r
+\r
+        internal Task DownloadFileTaskAsync(Uri uri, string fileName, CancellationToken cancellationToken, IProgress<DownloadProgressChangedEventArgs> progress)\r
+        {\r
+            cancellationToken.Register(CancelAsync);\r
+            DownloadProgressChangedEventHandler onDownloadProgressChanged = (o, e) => progress.Report(e);\r
+            this.DownloadProgressChanged += onDownloadProgressChanged;\r
+            return this.DownloadFileTaskAsync(uri, fileName).ContinueWith(t=>\r
+                {\r
+                    this.DownloadProgressChanged -= onDownloadProgressChanged;\r
+                });\r
+        }\r
+\r
+        internal Task<byte[]> DownloadDataTaskAsync(Uri uri, CancellationToken cancellationToken, IProgress<DownloadProgressChangedEventArgs> progress)\r
+        {\r
+            cancellationToken.Register(CancelAsync);\r
+            DownloadProgressChangedEventHandler onDownloadProgressChanged = (o, e) => progress.Report(e);\r
+            this.DownloadProgressChanged += onDownloadProgressChanged;\r
+            return this.DownloadDataTaskAsync(uri).ContinueWith(t =>\r
+            {\r
+                this.DownloadProgressChanged -= onDownloadProgressChanged;\r
+                return t.Result;\r
+            });\r
+        }\r
+    }\r
+\r
+    public class RetryException:Exception\r
+    {\r
+        public RetryException()\r
+            :base()\r
+        {\r
+            \r
+        }\r
+\r
+        public RetryException(string message)\r
+            :base(message)\r
+        {\r
+            \r
+        }\r
+\r
+        public RetryException(string message,Exception innerException)\r
+            :base(message,innerException)\r
+        {\r
+            \r
+        }\r
+\r
+        public RetryException(SerializationInfo info,StreamingContext context)\r
+            :base(info,context)\r
+        {\r
+            \r
+        }\r
+    }\r
+}\r