+#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.Net.Http;\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 RestHttpClient:HttpClient\r
+ {\r
+ private static readonly ILog Log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);\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
+ public RestHttpClient(HttpMessageHandler handler)\r
+ :base(handler)\r
+ {}\r
+\r
+ public RestHttpClient():base(new HttpClientHandler\r
+ {\r
+ AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,\r
+ UseCookies=true\r
+ })\r
+ { \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
+ //this.MaxResponseContentBufferSize = -1;\r
+ }\r
+\r
+ \r
+ \r
+\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
+ \r
+ public string GetStringWithRetry(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 = GetAsync(address).WithRetries(Timeout,actualRetries)\r
+ .ContinueWith(async r =>\r
+ {\r
+ var response = r.Result;\r
+ if (response.StatusCode == HttpStatusCode.NoContent)\r
+ return String.Empty;\r
+ if (response.StatusCode == HttpStatusCode.NotFound)\r
+ return null;\r
+ return await response.Content.ReadAsStringAsync();\r
+ }).Unwrap();\r
+\r
+ var result = task.Result;\r
+ return result;\r
+ }\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
+ \r
+ var task = Retry(GetAsync(actualAddress)).ContinueWith(async () =>\r
+ {\r
+ var response= await GetAsync(actualAddress); \r
+\r
+ if (response.StatusCode== HttpStatusCode.NoContent)\r
+ return String.Empty;\r
+ return await response.Content.ReadAsStringAsync();\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
+\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
+ DefaultRequestHeaders.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
+\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
+ \r
+/*\r
+ private Task<T> Retry2<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
+\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
+}\r