1 // -----------------------------------------------------------------------
2 // <copyright file="PithosClient.cs" company="Microsoft">
3 // TODO: Update copyright text.
5 // -----------------------------------------------------------------------
7 using System.Collections.Specialized;
8 using System.Diagnostics;
9 using System.Diagnostics.Contracts;
11 using System.Runtime.Serialization;
13 namespace Pithos.Network
16 using System.Collections.Generic;
21 /// TODO: Update summary.
23 public class PithosClient:WebClient
25 public int Timeout { get; set; }
27 public bool TimedOut { get; set; }
29 public HttpStatusCode StatusCode { get; private set; }
31 public string StatusDescription { get; set; }
34 public int Retries { get; set; }
36 private readonly Dictionary<string, string> _parameters=new Dictionary<string, string>();
37 public Dictionary<string, string> Parameters
39 get { return _parameters; }
42 public PithosClient():base()
48 public PithosClient(PithosClient other)
52 Timeout = other.Timeout;
53 Retries = other.Retries;
54 BaseAddress = other.BaseAddress;
57 foreach (var parameter in other.Parameters)
59 Parameters.Add(parameter.Key,parameter.Value);
62 this.Proxy = other.Proxy;
65 protected override WebRequest GetWebRequest(Uri address)
68 HttpWebRequest request = base.GetWebRequest(address) as HttpWebRequest;
69 request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
71 request.Timeout = Timeout;
75 protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result)
77 var response = (HttpWebResponse) base.GetWebResponse(request, result);
78 StatusCode=response.StatusCode;
79 StatusDescription=response.StatusDescription;
85 protected override WebResponse GetWebResponse(WebRequest request)
87 var response = (HttpWebResponse)base.GetWebResponse(request);
88 StatusCode = response.StatusCode;
89 StatusDescription = response.StatusDescription;
93 public string DownloadStringWithRetry(string address,int retries=0)
96 throw new ArgumentNullException("address");
98 var actualAddress = GetActualAddress(address);
100 var actualRetries = (retries == 0) ? Retries : retries;
103 var func = Retry(() =>
105 var uriString = String.Join("/", BaseAddress, actualAddress);
106 var content = base.DownloadString(uriString);
108 if (StatusCode == HttpStatusCode.NoContent)
118 public void Head(string address,int retries=0)
120 RetryWithoutContent(address, retries, "HEAD");
123 public void PutWithRetry(string address, int retries = 0)
125 RetryWithoutContent(address, retries, "PUT");
128 public void DeleteWithRetry(string address,int retries=0)
130 RetryWithoutContent(address, retries, "DELETE");
133 public string GetHeaderValue(string headerName)
135 var values=this.ResponseHeaders.GetValues(headerName);
137 throw new WebException(String.Format("The {0} header is missing", headerName));
142 private void RetryWithoutContent(string address, int retries, string method)
145 throw new ArgumentNullException("address");
147 var actualAddress = GetActualAddress(address);
149 var actualRetries = (retries == 0) ? Retries : retries;
150 var func = Retry(() =>
152 var uriString = String.Join("/",BaseAddress ,actualAddress);
153 var uri = new Uri(uriString);
154 var request = GetWebRequest(uri);
155 request.Method = method;
156 if (ResponseHeaders!=null)
157 ResponseHeaders.Clear();
159 var response = (HttpWebResponse)GetWebResponse(request);
160 StatusCode = response.StatusCode;
161 StatusDescription = response.StatusDescription;
170 private string GetActualAddress(string address)
172 if (Parameters.Count == 0)
174 var addressBuilder=new StringBuilder(address);
177 foreach (var parameter in Parameters)
180 addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value);
182 addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value);
185 return addressBuilder.ToString();
188 public string DownloadStringWithRetry(Uri address,int retries=0)
191 throw new ArgumentNullException("address");
193 var actualRetries = (retries == 0) ? Retries : retries;
194 var func = Retry(() =>
196 var content = base.DownloadString(address);
198 if (StatusCode == HttpStatusCode.NoContent)
210 /// Copies headers from another PithosClient
212 /// <param name="source">The PithosClient from which the headers are copied</param>
213 public void CopyHeaders(PithosClient source)
215 Contract.Requires(source != null, "source can't be null");
217 throw new ArgumentNullException("source", "source can't be null");
218 CopyHeaders(source.Headers,Headers);
222 /// Copies headers from one header collection to another
224 /// <param name="source">The source collection from which the headers are copied</param>
225 /// <param name="target">The target collection to which the headers are copied</param>
226 public static void CopyHeaders(WebHeaderCollection source,WebHeaderCollection target)
228 Contract.Requires(source != null, "source can't be null");
229 Contract.Requires(target != null, "target can't be null");
231 throw new ArgumentNullException("source", "source can't be null");
233 throw new ArgumentNullException("target", "target can't be null");
234 for (int i = 0; i < source.Count; i++)
236 target.Add(source.GetKey(i), source[i]);
240 public void AssertStatusOK(string message)
242 if (StatusCode >= HttpStatusCode.BadRequest)
243 throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription));
246 private Func<T> Retry<T>(Func< T> original, int retryCount)
256 catch (WebException e)
258 var statusCode = ((HttpWebResponse)e.Response).StatusCode;
259 this.StatusCode = statusCode;
263 case WebExceptionStatus.Timeout:
268 Trace.TraceError("[ERROR] Timed out too many times. {0}\n", e);
269 throw new RetryException("Timed out too many times.", e);
273 "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout,
277 case WebExceptionStatus.ProtocolError:
280 case HttpStatusCode.NotFound:
284 case HttpStatusCode.ServiceUnavailable:
290 Trace.TraceError("[ERROR] Failed too many times. {0}\n", e);
291 throw new RetryException("Failed too many times.", e);
295 "[RETRY] Failed due to 503. Will retry {0} more times\n{1}", retryCount, e);
313 public class RetryException:Exception
315 public RetryException()
321 public RetryException(string message)
327 public RetryException(string message,Exception innerException)
328 :base(message,innerException)
333 public RetryException(SerializationInfo info,StreamingContext context)