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 if (IfModifiedSince.HasValue)
72 request.IfModifiedSince = IfModifiedSince.Value;
73 request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;
75 request.Timeout = Timeout;
79 public DateTime? IfModifiedSince { get; set; }
81 protected override WebResponse GetWebResponse(WebRequest request, IAsyncResult result)
83 var response = (HttpWebResponse) base.GetWebResponse(request, result);
84 StatusCode=response.StatusCode;
85 StatusDescription=response.StatusDescription;
91 protected override WebResponse GetWebResponse(WebRequest request)
95 var response = (HttpWebResponse)base.GetWebResponse(request);
96 StatusCode = response.StatusCode;
97 StatusDescription = response.StatusDescription;
100 catch (WebException exc)
102 if (exc.Response!=null && exc.Response.ContentLength > 0)
104 string content = GetContent(exc.Response);
105 Trace.TraceError(content);
111 private static string GetContent(WebResponse webResponse)
114 using (var stream = webResponse.GetResponseStream())
115 using (var reader = new StreamReader(stream))
117 content = reader.ReadToEnd();
122 public string DownloadStringWithRetry(string address,int retries=0)
125 throw new ArgumentNullException("address");
127 var actualAddress = GetActualAddress(address);
129 TraceStart("GET",actualAddress);
131 var actualRetries = (retries == 0) ? Retries : retries;
135 var task = Retry(() =>
137 var uriString = String.Join("/", BaseAddress.TrimEnd('/'), actualAddress);
138 var content = base.DownloadString(uriString);
140 if (StatusCode == HttpStatusCode.NoContent)
146 var result = task.Result;
150 public void Head(string address,int retries=0)
152 RetryWithoutContent(address, retries, "HEAD");
155 public void PutWithRetry(string address, int retries = 0)
157 RetryWithoutContent(address, retries, "PUT");
160 public void DeleteWithRetry(string address,int retries=0)
162 RetryWithoutContent(address, retries, "DELETE");
165 public string GetHeaderValue(string headerName)
167 var values=this.ResponseHeaders.GetValues(headerName);
169 throw new WebException(String.Format("The {0} header is missing", headerName));
174 private void RetryWithoutContent(string address, int retries, string method)
177 throw new ArgumentNullException("address");
179 var actualAddress = GetActualAddress(address);
180 var actualRetries = (retries == 0) ? Retries : retries;
182 var task = Retry(() =>
184 var uriString = String.Join("/",BaseAddress ,actualAddress);
185 var uri = new Uri(uriString);
186 var request = GetWebRequest(uri);
187 request.Method = method;
188 if (ResponseHeaders!=null)
189 ResponseHeaders.Clear();
191 TraceStart(method, uriString);
193 var response = (HttpWebResponse)GetWebResponse(request);
194 StatusCode = response.StatusCode;
195 StatusDescription = response.StatusDescription;
204 /*private string RetryWithContent(string address, int retries, string method)
207 throw new ArgumentNullException("address");
209 var actualAddress = GetActualAddress(address);
210 var actualRetries = (retries == 0) ? Retries : retries;
212 var task = Retry(() =>
214 var uriString = String.Join("/",BaseAddress ,actualAddress);
215 var uri = new Uri(uriString);
217 var request = GetWebRequest(uri);
218 request.Method = method;
220 if (ResponseHeaders!=null)
221 ResponseHeaders.Clear();
223 TraceStart(method, uriString);
225 var getResponse = request.GetResponseAsync();
227 var setStatus= getResponse.ContinueWith(t =>
229 var response = (HttpWebResponse)t.Result;
230 StatusCode = response.StatusCode;
231 StatusDescription = response.StatusDescription;
235 var getData = setStatus.ContinueWith(t =>
237 var response = t.Result;
238 return response.GetResponseStream()
239 .ReadAllBytesAsync();
242 var data = getData.Result;
243 var content=Encoding.UTF8.GetString(data);
245 // var response = (HttpWebResponse)GetWebResponse(request);
249 StatusCode = response.StatusCode;
250 StatusDescription = response.StatusDescription;
260 private static void TraceStart(string method, string actualAddress)
262 Trace.WriteLine(String.Format("[{0}] {1} {2}", method, DateTime.Now, actualAddress));
265 private string GetActualAddress(string address)
267 if (Parameters.Count == 0)
269 var addressBuilder=new StringBuilder(address);
272 foreach (var parameter in Parameters)
275 addressBuilder.AppendFormat("?{0}={1}", parameter.Key, parameter.Value);
277 addressBuilder.AppendFormat("&{0}={1}", parameter.Key, parameter.Value);
280 return addressBuilder.ToString();
283 public string DownloadStringWithRetry(Uri address,int retries=0)
286 throw new ArgumentNullException("address");
288 var actualRetries = (retries == 0) ? Retries : retries;
289 var task = Retry(() =>
291 var content = base.DownloadString(address);
293 if (StatusCode == HttpStatusCode.NoContent)
299 var result = task.Result;
305 /// Copies headers from another RestClient
307 /// <param name="source">The RestClient from which the headers are copied</param>
308 public void CopyHeaders(RestClient source)
310 Contract.Requires(source != null, "source can't be null");
312 throw new ArgumentNullException("source", "source can't be null");
313 CopyHeaders(source.Headers,Headers);
317 /// Copies headers from one header collection to another
319 /// <param name="source">The source collection from which the headers are copied</param>
320 /// <param name="target">The target collection to which the headers are copied</param>
321 public static void CopyHeaders(WebHeaderCollection source,WebHeaderCollection target)
323 Contract.Requires(source != null, "source can't be null");
324 Contract.Requires(target != null, "target can't be null");
326 throw new ArgumentNullException("source", "source can't be null");
328 throw new ArgumentNullException("target", "target can't be null");
329 for (int i = 0; i < source.Count; i++)
331 target.Add(source.GetKey(i), source[i]);
335 public void AssertStatusOK(string message)
337 if (StatusCode >= HttpStatusCode.BadRequest)
338 throw new WebException(String.Format("{0} with code {1} - {2}", message, StatusCode, StatusDescription));
342 private Task<T> Retry<T>(Func<T> original, int retryCount, TaskCompletionSource<T> tcs = null)
345 tcs = new TaskCompletionSource<T>();
346 Task.Factory.StartNew(original).ContinueWith(_original =>
348 if (!_original.IsFaulted)
349 tcs.SetFromTask(_original);
352 var e = _original.Exception.InnerException;
353 var we = (e as WebException);
358 var statusCode = GetStatusCode(we);
360 //Return null for 404
361 if (statusCode == HttpStatusCode.NotFound)
362 tcs.SetResult(default(T));
363 //Retry for timeouts and service unavailable
364 else if (we.Status == WebExceptionStatus.Timeout ||
365 (we.Status == WebExceptionStatus.ProtocolError && statusCode == HttpStatusCode.ServiceUnavailable))
370 Trace.TraceError("[ERROR] Timed out too many times. {0}\n", e);
371 tcs.SetException(new RetryException("Timed out too many times.", e));
376 "[RETRY] Timed out after {0} ms. Will retry {1} more times\n{2}", Timeout,
378 Retry(original, retryCount - 1, tcs);
389 private HttpStatusCode GetStatusCode(WebException we)
391 var statusCode = HttpStatusCode.RequestTimeout;
392 if (we.Response != null)
394 statusCode = ((HttpWebResponse) we.Response).StatusCode;
395 this.StatusCode = statusCode;
401 public class RetryException:Exception
403 public RetryException()
409 public RetryException(string message)
415 public RetryException(string message,Exception innerException)
416 :base(message,innerException)
421 public RetryException(SerializationInfo info,StreamingContext context)