1 // -----------------------------------------------------------------------
2 // <copyright file="RestClient.cs" company="Microsoft">
3 // TODO: Update copyright text.
5 // -----------------------------------------------------------------------
7 using System.Collections.Specialized;
8 using System.Diagnostics;
9 using System.Diagnostics.Contracts;
12 using System.Runtime.Serialization;
13 using System.Threading.Tasks;
15 namespace Pithos.Network
18 using System.Collections.Generic;
23 /// TODO: Update summary.
25 public class RestClient:WebClient
27 public int Timeout { get; set; }
29 public bool TimedOut { get; set; }
31 public HttpStatusCode StatusCode { get; private set; }
33 public string StatusDescription { get; set; }
36 public int Retries { get; set; }
38 private readonly Dictionary<string, string> _parameters=new Dictionary<string, string>();
39 public Dictionary<string, string> Parameters
41 get { return _parameters; }
44 public RestClient():base()
50 public RestClient(RestClient other)
54 Timeout = other.Timeout;
55 Retries = other.Retries;
56 BaseAddress = other.BaseAddress;
58 foreach (var parameter in other.Parameters)
60 Parameters.Add(parameter.Key,parameter.Value);
63 this.Proxy = other.Proxy;
66 protected override WebRequest GetWebRequest(Uri address)
69 var webRequest = base.GetWebRequest(address);
70 var request = webRequest as HttpWebRequest;
71 request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
73 request.Timeout = Timeout;
77 protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result)
79 var response = (HttpWebResponse) base.GetWebResponse(request, result);
80 StatusCode=response.StatusCode;
81 StatusDescription=response.StatusDescription;
87 protected override WebResponse GetWebResponse(WebRequest request)
91 var response = (HttpWebResponse)base.GetWebResponse(request);
92 StatusCode = response.StatusCode;
93 StatusDescription = response.StatusDescription;
96 catch (WebException exc)
98 if (exc.Response!=null && exc.Response.ContentLength > 0)
100 string content = GetContent(exc.Response);
101 Trace.TraceError(content);
107 private static string GetContent(WebResponse webResponse)
110 using (var stream = webResponse.GetResponseStream())
111 using (var reader = new StreamReader(stream))
113 content = reader.ReadToEnd();
118 public string DownloadStringWithRetry(string address,int retries=0)
121 throw new ArgumentNullException("address");
123 var actualAddress = GetActualAddress(address);
125 TraceStart("GET",actualAddress);
127 var actualRetries = (retries == 0) ? Retries : retries;
131 var task = Retry(() =>
133 var uriString = String.Join("/", BaseAddress.TrimEnd('/'), actualAddress);
134 var content = base.DownloadString(uriString);
136 if (StatusCode == HttpStatusCode.NoContent)
142 var result = task.Result;
146 public void Head(string address,int retries=0)
148 RetryWithoutContent(address, retries, "HEAD");
151 public void PutWithRetry(string address, int retries = 0)
153 RetryWithoutContent(address, retries, "PUT");
156 public void DeleteWithRetry(string address,int retries=0)
158 RetryWithoutContent(address, retries, "DELETE");
161 public string GetHeaderValue(string headerName)
163 var values=this.ResponseHeaders.GetValues(headerName);
165 throw new WebException(String.Format("The {0} header is missing", headerName));
170 private void RetryWithoutContent(string address, int retries, string method)
173 throw new ArgumentNullException("address");
175 var actualAddress = GetActualAddress(address);
176 var actualRetries = (retries == 0) ? Retries : retries;
178 var task = Retry(() =>
180 var uriString = String.Join("/",BaseAddress ,actualAddress);
181 var uri = new Uri(uriString);
182 var request = GetWebRequest(uri);
183 request.Method = method;
184 if (ResponseHeaders!=null)
185 ResponseHeaders.Clear();
187 TraceStart(method, uriString);
189 var response = (HttpWebResponse)GetWebResponse(request);
190 StatusCode = response.StatusCode;
191 StatusDescription = response.StatusDescription;
200 /*private string RetryWithContent(string address, int retries, string method)
203 throw new ArgumentNullException("address");
205 var actualAddress = GetActualAddress(address);
206 var actualRetries = (retries == 0) ? Retries : retries;
208 var task = Retry(() =>
210 var uriString = String.Join("/",BaseAddress ,actualAddress);
211 var uri = new Uri(uriString);
213 var request = GetWebRequest(uri);
214 request.Method = method;
216 if (ResponseHeaders!=null)
217 ResponseHeaders.Clear();
219 TraceStart(method, uriString);
221 var getResponse = request.GetResponseAsync();
223 var setStatus= getResponse.ContinueWith(t =>
225 var response = (HttpWebResponse)t.Result;
226 StatusCode = response.StatusCode;
227 StatusDescription = response.StatusDescription;
231 var getData = setStatus.ContinueWith(t =>
233 var response = t.Result;
234 return response.GetResponseStream()
235 .ReadAllBytesAsync();
238 var data = getData.Result;
239 var content=Encoding.UTF8.GetString(data);
241 // var response = (HttpWebResponse)GetWebResponse(request);
245 StatusCode = response.StatusCode;
246 StatusDescription = response.StatusDescription;
256 private static void TraceStart(string method, string actualAddress)
258 Trace.WriteLine(String.Format("[{0}] {1} {2}", method, DateTime.Now, actualAddress));
261 private string GetActualAddress(string address)
263 if (Parameters.Count == 0)
265 var addressBuilder=new StringBuilder(address);
268 foreach (var parameter in Parameters)
271 addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value);
273 addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value);
276 return addressBuilder.ToString();
279 public string DownloadStringWithRetry(Uri address,int retries=0)
282 throw new ArgumentNullException("address");
284 var actualRetries = (retries == 0) ? Retries : retries;
285 var task = Retry(() =>
287 var content = base.DownloadString(address);
289 if (StatusCode == HttpStatusCode.NoContent)
295 var result = task.Result;
301 /// Copies headers from another RestClient
303 /// <param name="source">The RestClient from which the headers are copied</param>
304 public void CopyHeaders(RestClient source)
306 Contract.Requires(source != null, "source can't be null");
308 throw new ArgumentNullException("source", "source can't be null");
309 CopyHeaders(source.Headers,Headers);
313 /// Copies headers from one header collection to another
315 /// <param name="source">The source collection from which the headers are copied</param>
316 /// <param name="target">The target collection to which the headers are copied</param>
317 public static void CopyHeaders(WebHeaderCollection source,WebHeaderCollection target)
319 Contract.Requires(source != null, "source can't be null");
320 Contract.Requires(target != null, "target can't be null");
322 throw new ArgumentNullException("source", "source can't be null");
324 throw new ArgumentNullException("target", "target can't be null");
325 for (int i = 0; i < source.Count; i++)
327 target.Add(source.GetKey(i), source[i]);
331 public void AssertStatusOK(string message)
333 if (StatusCode >= HttpStatusCode.BadRequest)
334 throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription));
338 private Task<T> Retry<T>(Func<T> original, int retryCount, TaskCompletionSource<T> tcs = null)
341 tcs = new TaskCompletionSource<T>();
342 Task.Factory.StartNew(original).ContinueWith(_original =>
344 if (!_original.IsFaulted)
345 tcs.SetFromTask(_original);
348 var e = _original.Exception.InnerException;
349 var we = (e as WebException);
354 var statusCode = GetStatusCode(we);
356 //Return null for 404
357 if (statusCode == HttpStatusCode.NotFound)
358 tcs.SetResult(default(T));
359 //Retry for timeouts and service unavailable
360 else if (we.Status == WebExceptionStatus.Timeout ||
361 (we.Status == WebExceptionStatus.ProtocolError && statusCode == HttpStatusCode.ServiceUnavailable))
366 Trace.TraceError("[ERROR] Timed out too many times. {0}\n", e);
367 tcs.SetException(new RetryException("Timed out too many times.", e));
372 "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout,
374 Retry(original, retryCount - 1, tcs);
385 private HttpStatusCode GetStatusCode(WebException we)
387 var statusCode = HttpStatusCode.RequestTimeout;
388 if (we.Response != null)
390 statusCode = ((HttpWebResponse) we.Response).StatusCode;
391 this.StatusCode = statusCode;
397 public class RetryException:Exception
399 public RetryException()
405 public RetryException(string message)
411 public RetryException(string message,Exception innerException)
412 :base(message,innerException)
417 public RetryException(SerializationInfo info,StreamingContext context)